/* * This file is part of Skript. * * Skript is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Skript is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Skript. If not, see <http://www.gnu.org/licenses/>. * * * Copyright 2011-2014 Peter Güttinger * */ package ch.njol.skript.events; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.SkriptEventHandler; import ch.njol.skript.events.bukkit.ScheduledEvent; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SelfRegisteringSkriptEvent; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Trigger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Time; import ch.njol.util.Math2; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * @author Peter Güttinger */ @SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") public class EvtAtTime extends SelfRegisteringSkriptEvent implements Comparable<EvtAtTime> { static { Skript.registerEvent("*At Time", EvtAtTime.class, ScheduledEvent.class, "at %time% [in %worlds%]") .description("An event that occurs at a given <a href='../classes/#time'>minecraft time</a> in every world or only in specific worlds.") .examples("at 18:00", "at 7am in \"world\"") .since("1.3.4"); } private final static int CHECKPERIOD = 10; private final static class EvtAtInfo { public EvtAtInfo() {} int lastTick; // as Bukkit's scheduler is inconsistent this saves the exact tick when the events were last checked int currentIndex; ArrayList<EvtAtTime> list = new ArrayList<EvtAtTime>(); } final static HashMap<World, EvtAtInfo> triggers = new HashMap<World, EvtAtInfo>(); @Nullable private Trigger t; int tick; @SuppressWarnings("null") private transient World[] worlds; /** * null if all worlds */ @Nullable private String[] worldNames = null; @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(final Literal<?>[] args, final int matchedPattern, final ParseResult parser) { tick = ((Literal<Time>) args[0]).getSingle().getTicks(); worlds = args[1] == null ? Bukkit.getWorlds().toArray(new World[0]) : ((Literal<World>) args[1]).getAll(); if (args[1] != null) { worldNames = new String[worlds.length]; for (int i = 0; i < worlds.length; i++) worldNames[i] = worlds[i].getName(); } return true; } private static int taskID = -1; private static void registerListener() { if (taskID != -1) return; taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(Skript.getInstance(), new Runnable() { @SuppressWarnings("null") @Override public void run() { for (final Entry<World, EvtAtInfo> e : triggers.entrySet()) { final EvtAtInfo i = e.getValue(); final int tick = (int) e.getKey().getTime(); if (i.lastTick == tick) // stupid Bukkit scheduler continue; if (i.lastTick + CHECKPERIOD * 2 < tick || i.lastTick > tick && i.lastTick - 24000 + CHECKPERIOD * 2 < tick) { // time changed, e.g. by a command or plugin i.lastTick = Math2.mod(tick - CHECKPERIOD, 24000); } final boolean midnight = i.lastTick > tick; // actually 6:00 if (midnight) i.lastTick -= 24000; final int startIndex = i.currentIndex; while (true) { final EvtAtTime next = i.list.get(i.currentIndex); final int nextTick = midnight && next.tick > 12000 ? next.tick - 24000 : next.tick; if (i.lastTick < nextTick && nextTick <= tick) { next.execute(e.getKey()); i.currentIndex++; if (i.currentIndex == i.list.size()) i.currentIndex = 0; if (i.currentIndex == startIndex) // all events executed at once break; } else { break; } } i.lastTick = tick; } } }, 0, CHECKPERIOD); } void execute(final World w) { final Trigger t = this.t; if (t == null) { assert false; return; } final ScheduledEvent e = new ScheduledEvent(w); SkriptEventHandler.logEventStart(e); SkriptEventHandler.logTriggerEnd(t); t.execute(e); SkriptEventHandler.logTriggerEnd(t); SkriptEventHandler.logEventEnd(); } @Override public void register(final Trigger t) { this.t = t; for (final World w : worlds) { EvtAtInfo i = triggers.get(w); if (i == null) { triggers.put(w, i = new EvtAtInfo()); i.lastTick = (int) w.getTime() - 1; } i.list.add(this); Collections.sort(i.list); } registerListener(); } @Override public void unregister(final Trigger t) { assert t == this.t; this.t = null; final Iterator<EvtAtInfo> iter = triggers.values().iterator(); while (iter.hasNext()) { final EvtAtInfo i = iter.next(); i.list.remove(this); if (i.currentIndex >= i.list.size()) i.currentIndex--; if (i.list.isEmpty()) iter.remove(); } if (triggers.isEmpty()) unregisterAll(); } @Override public void unregisterAll() { if (taskID != -1) Bukkit.getScheduler().cancelTask(taskID); t = null; taskID = -1; triggers.clear(); } @Override public String toString(final @Nullable Event e, final boolean debug) { return "at " + Time.toString(tick) + " in worlds " + Classes.toString(worlds, true); } @Override public int compareTo(final @Nullable EvtAtTime e) { return e == null ? tick : tick - e.tick; } }