diff --git a/Plugins/Mineplex.Core/pom.xml b/Plugins/Mineplex.Core/pom.xml index a2d2f6e3e..afbd561ec 100644 --- a/Plugins/Mineplex.Core/pom.xml +++ b/Plugins/Mineplex.Core/pom.xml @@ -60,6 +60,11 @@ core 3.3.0 + + junit + junit + test + diff --git a/Plugins/Mineplex.Core/src/mineplex/core/MiniPlugin.java b/Plugins/Mineplex.Core/src/mineplex/core/MiniPlugin.java index 631c05202..9f76c5201 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/MiniPlugin.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/MiniPlugin.java @@ -7,6 +7,9 @@ import mineplex.core.common.util.NautHashMap; import mineplex.core.common.util.UtilServer; import mineplex.core.common.util.UtilTime; import mineplex.core.common.util.UtilTime.TimeUnit; +import mineplex.core.lifetimes.Lifetime; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.lifetimes.SimpleLifetime; import mineplex.core.thread.ThreadPool; import org.bukkit.Bukkit; @@ -27,12 +30,16 @@ import org.bukkit.scheduler.BukkitTask; *

* This way, we can reflectively create them during {@link #require} when they do not exist, leading to much cleaner code */ -public abstract class MiniPlugin implements Listener +public abstract class MiniPlugin implements Listener, Lifetimed { + // As MiniPlugins can technically be disabled at any time, each + // has its own unique Lifetime. If MiniPlugins are declared + // to never be able to be disabled, then a "Singleton" Lifetime + // could be shared between all of them. + private final SimpleLifetime _lifetime = new SimpleLifetime(); protected String _moduleName = "Default"; protected JavaPlugin _plugin; - protected NautHashMap _commands; - + protected long _initializedTime; public MiniPlugin(String moduleName) @@ -47,8 +54,7 @@ public abstract class MiniPlugin implements Listener _initializedTime = System.currentTimeMillis(); - _commands = new NautHashMap<>(); - + _lifetime.start(); onEnable(); registerEvents(this); @@ -250,4 +256,10 @@ public abstract class MiniPlugin implements Listener { return Managers.require(clazz); } + + @Override + public Lifetime getLifetime() + { + return _lifetime; + } } diff --git a/Plugins/Mineplex.Core/src/mineplex/core/command/ICommand.java b/Plugins/Mineplex.Core/src/mineplex/core/command/ICommand.java index 463a7631f..becbdfee5 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/command/ICommand.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/command/ICommand.java @@ -5,10 +5,11 @@ import java.util.List; import mineplex.core.common.Rank; +import mineplex.core.lifetimes.Component; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -public interface ICommand +public interface ICommand extends Component { void SetCommandCenter(CommandCenter commandCenter); void Execute(Player caller, String[] args); @@ -21,4 +22,16 @@ public interface ICommand Rank[] GetSpecificRanks(); List onTabComplete(CommandSender sender, String commandLabel, String[] args); + + @Override + default void activate() + { + CommandCenter.Instance.addCommand(this); + } + + @Override + default void deactivate() + { + CommandCenter.Instance.removeCommand(this); + } } diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Component.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Component.java new file mode 100644 index 000000000..cee7da78f --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Component.java @@ -0,0 +1,21 @@ +package mineplex.core.lifetimes; + +/** + * A Component defines behavior that can exist within a Lifetime. Components + * should have no impact upon the game while not active. + */ +public interface Component +{ + /** + * Activates the Component, performing any sort of required initialization. + * Components may be activated and deactivated multiple times, however a component + * will not be activated more than once without subsequent calls to deactivate. + */ + void activate(); + + /** + * Deactivates the Component, disabling any sort of functionality it provides + * and performing clean up. A Component may be subsequently reactivated. + */ + void deactivate(); +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetime.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetime.java new file mode 100644 index 000000000..1cb578bc0 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetime.java @@ -0,0 +1,29 @@ +package mineplex.core.lifetimes; + +/** + * A Lifetime represents a duration for which a collection of Components + * will be active. While Lifetime does contain a method for registering + * instantiated Components, individual Lifetimes may have unique + * strategies for creating Components and activating them. Lifetime + * doesn't provide any guarantee of Component activation or deactivation + * order. Implementations of Lifetime, however, may. + *

+ * Lifetime doesn't provide mechanisms for beginning or ending a Lifetime. + * This is provided by the various implementations of Lifetime, as it varies + * between the implementations and is functionality that most consumers of + * Lifetimes will not need. + */ +public interface Lifetime +{ + /** + * Registers the provided component with this Lifetime. If the Lifetime + * is currently active, then the Component will be immediately activated. + * @param component the component to register + */ + void register(Component component); + /** + * Gets whether the Lifetime is currently active. + * @return true if the Lifetime is active + */ + boolean isActive(); +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetimed.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetimed.java new file mode 100644 index 000000000..fa08dd533 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/Lifetimed.java @@ -0,0 +1,19 @@ +package mineplex.core.lifetimes; + +/** + * Represents an object that is associated with a specific lifetime. + * Multiple Lifetimed objects may be associated with the same Lifetime. + * As a roughly generalized explanation, any time functionality should + * be enabled(whether its a command, listener, etc.) it should be registered + * as a Component of a Lifetime. Any object wishing to enable functionality + * that is associated with this Lifetimed object should do so with the Lifetime + * returned by {@link #getLifetime()}. + */ +public interface Lifetimed +{ + /** + * Gets the Lifetime associated with this Lifetimed object. + * @return non-null Lifetime + */ + Lifetime getLifetime(); +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/ListenerComponent.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/ListenerComponent.java new file mode 100644 index 000000000..d6b664fb4 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/ListenerComponent.java @@ -0,0 +1,54 @@ +package mineplex.core.lifetimes; + +import mineplex.core.common.util.UtilServer; +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +/** + * A convenience class for Components that are a Listener. ListenerComponent + * can either be used as a wrapper class for a specific Listener, or can be + * extended by another Component to provide event registration. + */ +public class ListenerComponent implements Component, Listener +{ + private final Listener _listener; + + /** + * Creates a ListenerComponent that registers the provided Listener when + * activated and unregisters it when deactivated. The newly created + * ListenerComponent will not be registered as a Listener. When a + * ListenerComponent is created with this constructor, it is effectively + * a wrapper to bind a Listener to a specific lifetime. + * + * @param listener non-null listener to wrap + * @throws IllegalArgumentException if listener is null + */ + public ListenerComponent(Listener listener) throws IllegalArgumentException + { + Validate.notNull(listener); + _listener = listener; + } + + /** + * Creates a ListenerComponent that registers itself when activated and + * unregisters itself when deactivated. + */ + public ListenerComponent() + { + _listener = this; + } + + @Override + public void activate() + { + Bukkit.getPluginManager().registerEvents(_listener, UtilServer.getPlugin()); + } + + @Override + public void deactivate() + { + HandlerList.unregisterAll(_listener); + } +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedComponent.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedComponent.java new file mode 100644 index 000000000..417bf56b1 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedComponent.java @@ -0,0 +1,7 @@ +package mineplex.core.lifetimes; + +public interface PhasedComponent extends Component +{ + + void setPhase(T phase); +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedLifetime.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedLifetime.java new file mode 100644 index 000000000..b9760e761 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/PhasedLifetime.java @@ -0,0 +1,289 @@ +package mineplex.core.lifetimes; + +import com.google.common.base.Preconditions; +import mineplex.core.common.util.UtilServer; +import org.apache.commons.lang.Validate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * A PhasedLifetime is a lifetime that is composed of several + * smaller lifetimes, referred to as phases. This class is provided + * in order to support a system in which Components may exist within multiple + * Lifetimes. PhasedLifetime is not thread-safe. + *

+ * Registering a Component will register it for the entirety of this Lifetime, + * unless registered with {@link #register(Component, Iterable)}. Special behavior + * is provided for instances of {@link PhasedComponent}. See {@link #register(Component, + * Iterable)} for more information. Components registered using {@link + * #register(Component)} are registered for the entire duration of the Lifetime. + */ +public class PhasedLifetime implements Lifetime +{ + private final Map> _phases = new HashMap<>(); + private final List _global = new ArrayList<>(); + private boolean _active = false; + private T _current; + + /** + * Registers the Component for all phases within the provided Iterable, + * and creates a Lifetime that is active during all those phases, and + * therefore identical to when the provided Component is active. If a change + * occurs from a Phase that the Component is active in to another phase that + * the component is active in, it will not be disabled. When this + * Lifetime ends, all Lifetimes created by this Lifetime also end, as all + * phases are considered over. + *

+ * If the Component is an instance of PhasedComponent, then any phase change + * in which one of the phases is within the provided Iterable of phases will + * result in an invocation of {@link PhasedComponent#setPhase(Object)}. This + * should not be used as a mechanism to detect when the component is being + * disabled, but rather as a means to provide specific behavior when that + * phase change occurs. If a phase change drastically changes the behavior + * of a Component such that not all functionality is active for some phases + * that a Component is registered for, you should consider refactoring that + * Component into two separate Components. + *

+ * As an example, assume that we have a PhasedLifetime with phases A-F, + * and a PhasedComponent is registered for phases A, B, and E. The chain + * of events would be as followed(italic indicates an event call to the + * PhasedComponent, an activation, or deactivation). + *

+ *

+ * If phases contains no elements, then the Component will not be + * registered. + * @param component non-null Component being registered + * @param phases non-null Iterable of phases to register the Component for + * @return a Lifetime corresponding to when the Component is active + * @throws IllegalArgumentException if component or phases is null + */ + public Lifetime register(Component component, Iterable phases) throws IllegalArgumentException { + Validate.notNull(component, "Component cannot be null"); + Validate.notNull(phases, "Phases cannot be null"); + RegisteredComponent rComponent = new RegisteredComponent(component); + for (T phase : phases) + { + _phases.computeIfAbsent(phase, (p) -> new ArrayList<>()).add(rComponent); + if (Objects.equals(phase, _current)) + { + rComponent.start(); + if (component instanceof PhasedComponent) + { + ((PhasedComponent) component).setPhase(phase); + } + } + } + return rComponent; + } + + /** + * Starts the Lifetime, activating all components that are active for + * the entire lifetime, and then activating all components that are part + * of the provided phase. + * @param phase non-null phase to start + * @throws IllegalArgumentException if phase is null + * @throws IllegalStateException if the Lifetime is currently active + */ + public void start(T phase) throws IllegalArgumentException, IllegalStateException + { + Validate.notNull(phase, "phase cannot be null"); + Preconditions.checkState(!_active, "Lifetime already started"); + _active = true; + _global.forEach(PhasedLifetime::active); + setPhase(phase); + } + + /** + * Ends the Lifetime, deactivating all components in the current phase + * and then deactivating all components that are active for the entire Lifetime. + * A Lifetime may be subsequently reactivated after it has ended. + * @throws IllegalStateException if the lifetime isn't active + */ + public void end() throws IllegalStateException + { + Preconditions.checkState(_active, "Lifetime not active"); + List toDisable = _phases.get(getPhase()); + if (toDisable != null) + { + toDisable.forEach(RegisteredComponent::end); + } + _global.forEach(PhasedLifetime::deactive); + _active = false; + _current = null; + } + + /** + * Sets the current phase to the provided value, activating components + * that are active in the phase and not currently active, and deactiving + * components that were previously active and are not registered for the + * provided phase. + * @param phase non-null + * @throws IllegalStateException if the Lifetime isn't active + * @throws IllegalArgumentException if the phase equals the current phase + * or is null + */ + public void setPhase(T phase) throws IllegalStateException, IllegalArgumentException + { + Preconditions.checkState(_active, "Lifetime not active"); + Validate.isTrue(!Objects.equals(phase, _current), "Can't set the phase to the current phase"); + Validate.notNull(phase, "the phase cannot be null"); + T oldPhase = getPhase(); + _current = phase; + List old = _phases.get(oldPhase); + List nextPhase = _phases.get(phase); + + for (Component c : _global) + { + if (c instanceof PhasedComponent) + { + ((PhasedComponent) c).setPhase(phase); + } + } + // Disable components that were active in the last phase but not in the next phase. + if (old != null) + { + List toDisable = new ArrayList<>(); + toDisable.addAll(old); + // Components that are in the next phase shouldn't be disabled. + if (nextPhase != null) + { + toDisable.removeAll(nextPhase); + } + + // Ensure that all old ones get a setPhase call before disabling them. + for (RegisteredComponent r : toDisable) + { + if (r.getComponent() instanceof PhasedComponent) + { + ((PhasedComponent) r.getComponent()).setPhase(phase); + } + } + toDisable.forEach(RegisteredComponent::end); + } + if (nextPhase != null) + { + // New but not old + List toActivate = new ArrayList<>(); + toActivate.addAll(nextPhase); + // Ensure that all components from last phase don't end up getting activated again. + if (old != null) + { + toActivate.removeAll(old); + } + // Start all the new ones + toActivate.forEach(RegisteredComponent::start); + // Give every remaining component a call to setPhase + for (RegisteredComponent r : nextPhase) + { + if (r.getComponent() instanceof PhasedComponent) + { + ((PhasedComponent) r.getComponent()).setPhase(phase); + } + } + } + } + + private static void active(Component component) + { + try + { + component.activate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to activate component: " + component); + } + } + private static void deactive(Component component) + { + try + { + component.deactivate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to deactivate component: " + component); + } + } + /** + * Gets the current Phase. If this PhasedLifetime isn't active then + * the current phase is null. + * @return the current phase, null if not active + */ + public T getPhase() + { + return _current; + } + + /** + * {@inheritDoc} + * If the Component is an instance of PhasedComponent, it will receive + * notifications of phase changes prior to any components registered + * with specific phases. Global components will also be deactivated last. + * @param component the component to register + */ + @Override + public void register(Component component) + { + _global.add(component); + if (this.isActive()) + { + component.activate(); + } + } + + @Override + public boolean isActive() + { + return _active; + } + + private static class RegisteredComponent extends SimpleLifetime implements Lifetime { + private Component _component; + + public RegisteredComponent(Component component) + { + this._component = component; + } + + public Component getComponent() + { + return _component; + } + + @Override + public void start() throws IllegalStateException + { + PhasedLifetime.active(_component); + super.start(); + } + + @Override + public void end() throws IllegalStateException + { + super.end(); + PhasedLifetime.deactive(_component); + } + } +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/SimpleLifetime.java b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/SimpleLifetime.java new file mode 100644 index 000000000..28dcfe45e --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/lifetimes/SimpleLifetime.java @@ -0,0 +1,96 @@ +package mineplex.core.lifetimes; + +import mineplex.core.common.util.UtilServer; +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +/** + * A Lifetime that is not thread-safe and is essentially a list of Components. + * All components are activated in the order that they are registered. All + * components are deactivated in the reverse order that they are registered. + * Components may not be registered during invocations of start or end. If a + * Component throws an exception during activation it will be logged, however the + * Component will still only be disabled once all other Components are disabled. + */ +public class SimpleLifetime implements Lifetime +{ + protected final List _components = new ArrayList<>(); + protected boolean _active = false; + @Override + public void register(Component component) + { + this._components.add(component); + if (this.isActive()) + { + try + { + component.activate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to active component: " + component); + } + } + } + + @Override + public boolean isActive() + { + return _active; + } + + /** + * Starts the SimpleLifetime, activating all components in the order that + * they were registered. A SimpleLifetime may be started multiple times, + * so long that every invocation of start after the first is preceded by + * an invocation of {@link #end()}. If a Component throws an exception + * during activation the exception will be logged, however no specific + * error-handling mechanism is provided. The Component will still be + * considered active and will be deactivated with all other Components. + * @throws IllegalStateException if currently active + */ + public void start() throws IllegalStateException + { + Validate.isTrue(!_active); + _active = true; + for (Component component : _components) + { + try + { + component.activate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to active component: " + component); + } + } + } + + /** + * Deactivates all components in the reverse order that they were registered. + * Any exception thrown by a Component while being deactivated will be logged, + * however no exception handling mechanism is provided. + * @throws IllegalStateException if not currently active + */ + public void end() throws IllegalStateException + { + Validate.isTrue(_active); + _active = false; + ListIterator reverseIterator = _components.listIterator(_components.size()); + while (reverseIterator.hasPrevious()) + { + Component component = reverseIterator.previous(); + try + { + component.deactivate(); + } catch (Exception e) + { + e.printStackTrace(); + UtilServer.getPlugin().getLogger().severe("Failed to deactivate component: " + component); + } + } + } +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/shop/ShopBase.java b/Plugins/Mineplex.Core/src/mineplex/core/shop/ShopBase.java index e9d26ad8e..6ee09a020 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/shop/ShopBase.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/shop/ShopBase.java @@ -1,13 +1,15 @@ package mineplex.core.shop; -import mineplex.core.MiniPlugin; import mineplex.core.account.CoreClientManager; import mineplex.core.common.util.NautHashMap; import mineplex.core.donation.DonationManager; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.lifetimes.ListenerComponent; import mineplex.core.npc.event.NpcDamageByEntityEvent; import mineplex.core.npc.event.NpcInteractEntityEvent; import mineplex.core.shop.page.ShopPageBase; import net.minecraft.server.v1_8_R3.EntityPlayer; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_8_R3.event.CraftEventFactory; @@ -16,7 +18,6 @@ import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; @@ -29,7 +30,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -public abstract class ShopBase implements Listener +public abstract class ShopBase extends ListenerComponent { private NautHashMap _errorThrottling; private NautHashMap _purchaseBlock; @@ -52,7 +53,7 @@ public abstract class ShopBase implements Listene _errorThrottling = new NautHashMap(); _purchaseBlock = new NautHashMap(); - _plugin.registerEvents(this); + _plugin.getLifetime().register(this); } @EventHandler(priority = EventPriority.LOWEST) @@ -150,14 +151,7 @@ public abstract class ShopBase implements Listene { if (_playerPageMap.containsKey(event.getPlayer().getUniqueId()) && _playerPageMap.get(event.getPlayer().getUniqueId()).getTitle() != null && _playerPageMap.get(event.getPlayer().getUniqueId()).getTitle().equalsIgnoreCase(event.getInventory().getTitle())) { - _playerPageMap.get(event.getPlayer().getUniqueId()).playerClosed(); - _playerPageMap.get(event.getPlayer().getUniqueId()).dispose(); - - _playerPageMap.remove(event.getPlayer().getUniqueId()); - - closeShopForPlayer((Player) event.getPlayer()); - - _openedShop.remove(event.getPlayer().getUniqueId()); + removePlayer((Player) event.getPlayer()); } } @@ -169,17 +163,39 @@ public abstract class ShopBase implements Listene if (_playerPageMap.containsKey(event.getPlayer().getUniqueId()) && _playerPageMap.get(event.getPlayer().getUniqueId()).getTitle() != null && _playerPageMap.get(event.getPlayer().getUniqueId()).getTitle().equalsIgnoreCase(event.getInventory().getTitle())) { - _playerPageMap.get(event.getPlayer().getUniqueId()).playerClosed(); - _playerPageMap.get(event.getPlayer().getUniqueId()).dispose(); - - _playerPageMap.remove(event.getPlayer().getUniqueId()); - - closeShopForPlayer((Player) event.getPlayer()); - - _openedShop.remove(event.getPlayer().getUniqueId()); + removePlayer((Player) event.getPlayer()); } } - + + protected void removePlayer(Player player) + { + _playerPageMap.get(player.getUniqueId()).playerClosed(); + _playerPageMap.get(player.getUniqueId()).dispose(); + + _playerPageMap.remove(player.getUniqueId()); + + closeShopForPlayer(player); + + _openedShop.remove(player.getUniqueId()); + } + + @Override + public void deactivate() + { + super.deactivate(); + _playerPageMap.entrySet().stream().map(Map.Entry::getKey).map(Bukkit::getPlayer).forEach(p -> { + if (_playerPageMap.get(p.getName()).getTitle().equals(p.getOpenInventory().getTitle())) + { + p.closeInventory(); + } + removePlayer(p); + }); + _playerPageMap.clear(); + _openedShop.clear(); + _purchaseBlock.clear(); + _errorThrottling.clear(); + } + protected boolean canOpenShop(Player player) { return true; @@ -194,15 +210,8 @@ public abstract class ShopBase implements Listene { if (_playerPageMap.containsKey(event.getPlayer().getUniqueId())) { - _playerPageMap.get(event.getPlayer().getUniqueId()).playerClosed(); - _playerPageMap.get(event.getPlayer().getUniqueId()).dispose(); - + removePlayer(event.getPlayer()); event.getPlayer().closeInventory(); - closeShopForPlayer(event.getPlayer()); - - _playerPageMap.remove(event.getPlayer().getUniqueId()); - - _openedShop.remove(event.getPlayer().getUniqueId()); } } diff --git a/Plugins/Mineplex.Core/src/mineplex/core/shop/page/ShopPageBase.java b/Plugins/Mineplex.Core/src/mineplex/core/shop/page/ShopPageBase.java index 7f1a8c525..086506dfd 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/shop/page/ShopPageBase.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/shop/page/ShopPageBase.java @@ -1,5 +1,14 @@ package mineplex.core.shop.page; + +import mineplex.core.account.CoreClient; +import mineplex.core.account.CoreClientManager; +import mineplex.core.common.util.NautHashMap; +import mineplex.core.common.util.UtilInv; +import mineplex.core.donation.DonationManager; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.shop.ShopBase; +import mineplex.core.shop.item.IButton; import org.bukkit.Sound; import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftInventoryCustom; @@ -11,16 +20,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import mineplex.core.MiniPlugin; -import mineplex.core.account.CoreClient; -import mineplex.core.account.CoreClientManager; -import mineplex.core.common.util.NautHashMap; -import mineplex.core.common.util.UtilInv; -import mineplex.core.donation.DonationManager; -import mineplex.core.shop.ShopBase; -import mineplex.core.shop.item.IButton; - -public abstract class ShopPageBase> extends CraftInventoryCustom implements Listener +public abstract class ShopPageBase> extends CraftInventoryCustom implements Listener { protected PluginType _plugin; protected CoreClientManager _clientManager; diff --git a/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/LoggingComponent.java b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/LoggingComponent.java new file mode 100644 index 000000000..d2f61d538 --- /dev/null +++ b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/LoggingComponent.java @@ -0,0 +1,32 @@ +package mineplex.core.lifetimes; + +import java.util.List; + +public class LoggingComponent implements PhasedComponent { + private final List _events; + private final String _name; + + public LoggingComponent(List events, String name) + { + _events = events; + this._name = name; + } + + @Override + public void activate() + { + _events.add(this._name + " activated"); + } + + @Override + public void deactivate() + { + _events.add(this._name + " deactivated"); + } + + @Override + public void setPhase(Object phase) + { + _events.add(this._name + " setPhase " + phase); + } +} \ No newline at end of file diff --git a/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/PhasedLifetimeTest.java b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/PhasedLifetimeTest.java new file mode 100644 index 000000000..3d62001c8 --- /dev/null +++ b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/PhasedLifetimeTest.java @@ -0,0 +1,99 @@ +package mineplex.core.lifetimes; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class PhasedLifetimeTest +{ + PhasedLifetime _lifetime = new PhasedLifetime<>(); + List _events = new ArrayList<>(); + @Test + public void testTwoPhaseComponent() + { + Assert.assertFalse(_lifetime.isActive()); + _lifetime.register(new LoggingComponent(_events, "component"), Arrays.asList(Phase.A, Phase.B)); + _lifetime.start(Phase.A); + Assert.assertTrue(_lifetime.isActive()); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A"), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B"), _events); + _lifetime.setPhase(Phase.C); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B", "component setPhase C", "component deactivated"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B", "component setPhase C", "component deactivated"), _events); + Assert.assertFalse(_lifetime.isActive()); + } + @Test + public void testGlobalComponent() + { + _lifetime.register(new LoggingComponent(_events, "component")); + _lifetime.start(Phase.A); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A"), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B", "component deactivated"), _events); + } + + @Test + public void testLateRegistration() + { + _lifetime.start(Phase.A); + _lifetime.register(new LoggingComponent(_events, "component"), Arrays.asList(Phase.A, Phase.B)); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A"), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase A", "component setPhase B", "component deactivated"), _events); + } + @Test + public void testSinglePhase() + { + _lifetime.register(new LoggingComponent(_events, "component"), Collections.singletonList(Phase.B)); + _lifetime.start(Phase.A); + Assert.assertEquals(Collections.emptyList(), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B"), _events); + _lifetime.setPhase(Phase.C); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B", "component setPhase C", "component deactivated"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B", "component setPhase C", "component deactivated"), _events); + } + @Test + public void testComponentLifetimes() + { + _lifetime.register(new LoggingComponent(_events, "component"), Collections.singletonList(Phase.B)).register(new LoggingComponent(_events, "child")); + _lifetime.start(Phase.A); + Assert.assertEquals(Collections.emptyList(), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "child activated", "component setPhase B"), _events); + _lifetime.setPhase(Phase.C); + Assert.assertEquals(Arrays.asList("component activated", "child activated","component setPhase B", "component setPhase C", "child deactivated", "component deactivated"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "child activated", "component setPhase B", "component setPhase C", "child deactivated", "component deactivated"), _events); + } + @Test + public void testEarlyShutdown() + { + _lifetime.register(new LoggingComponent(_events, "component"), Arrays.asList(Phase.B, Phase.C)); + _lifetime.start(Phase.A); + Assert.assertEquals(Collections.emptyList(), _events); + _lifetime.setPhase(Phase.B); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B"), _events); + _lifetime.end(); + Assert.assertEquals(Arrays.asList("component activated", "component setPhase B", "component deactivated"), _events); + + } + enum Phase + { + A, + B, + C, + ; + } +} diff --git a/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/SimpleLifetimeTest.java b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/SimpleLifetimeTest.java new file mode 100644 index 000000000..061a1decc --- /dev/null +++ b/Plugins/Mineplex.Core/test/mineplex/core/lifetimes/SimpleLifetimeTest.java @@ -0,0 +1,41 @@ +package mineplex.core.lifetimes; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SimpleLifetimeTest +{ + private final SimpleLifetime _lifetime = new SimpleLifetime(); + private final List _events = new ArrayList<>(); + @Test + public void testAddition() + { + _lifetime.register(new LoggingComponent(_events,"a")); + _lifetime.start(); + _lifetime.end(); + Assert.assertEquals(_events, Arrays.asList("a activated", "a deactivated")); + } + @Test + public void testLateAddition() + { + _lifetime.start(); + _lifetime.register(new LoggingComponent(_events,"a")); + _lifetime.end(); + Assert.assertEquals(_events, Arrays.asList("a activated", "a deactivated")); + } + @Test + public void testActivationOrder() + { + _lifetime.register(new LoggingComponent(_events,"a")); + _lifetime.register(new LoggingComponent(_events,"b")); + _lifetime.start(); + _lifetime.end(); + Assert.assertEquals(_events, Arrays.asList("a activated", "b activated", "b deactivated", "a deactivated")); + } + + +} diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/Game.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/Game.java index d2864e5e5..c008a6ae6 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/Game.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/Game.java @@ -13,6 +13,9 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.lifetimes.ListenerComponent; +import mineplex.core.lifetimes.PhasedLifetime; import org.apache.commons.lang3.tuple.Triple; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -109,9 +112,8 @@ import nautilus.game.arcade.world.WorldData; import net.minecraft.server.v1_8_R3.EntityItem; import net.minecraft.server.v1_8_R3.PacketPlayInUseEntity; -public abstract class Game implements Listener +public abstract class Game extends ListenerComponent implements Lifetimed { - private final static int MAX_TICK_SPEED_MEASUREMENT = 40; public long getGameLiveTime() @@ -135,6 +137,7 @@ public abstract class Game implements Listener private GameType _gameType; protected String[] _gameDesc; + private PhasedLifetime _lifetime = new PhasedLifetime<>(); // Map private HashMap> _files; @@ -387,6 +390,7 @@ public abstract class Game implements Listener { Manager = manager; + _lifetime.register(this); // Player List UtilTabTitle.broadcastHeaderAndFooter(C.cGold + C.Bold + gameType.GetName(), "Visit " + C.cGreen + "www.mineplex.com" + ChatColor.RESET + " for News, Forums and Shop"); @@ -760,6 +764,9 @@ public abstract class Game implements Listener for (Player player : UtilServer.getPlayers()) player.leaveVehicle(); + + _lifetime.setPhase(state); + // Event GameStateChangeEvent stateEvent = new GameStateChangeEvent(this, state); UtilServer.getServer().getPluginManager().callEvent(stateEvent); @@ -2347,7 +2354,11 @@ public abstract class Game implements Listener public void disable() { + cleanupModules(); + cleanupCommands(); Managers.get(AntiHack.class).resetIgnoredChecks(); + getLifetime().end(); + getStatTrackers().forEach(HandlerList::unregisterAll); } @EventHandler @@ -2455,5 +2466,10 @@ public abstract class Game implements Listener { return clazz.cast(_modules.get(clazz)); } - + + @Override + public PhasedLifetime getLifetime() + { + return _lifetime; + } } diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/GameComponent.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/GameComponent.java new file mode 100644 index 000000000..00118f80c --- /dev/null +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/GameComponent.java @@ -0,0 +1,31 @@ +package nautilus.game.arcade.game; + +import mineplex.core.lifetimes.Lifetime; +import mineplex.core.lifetimes.Lifetimed; +import mineplex.core.lifetimes.ListenerComponent; + +import java.util.Arrays; + +public class GameComponent extends ListenerComponent implements Lifetimed +{ + private final Lifetime _lifetime; + + public GameComponent(Game game, Game.GameState...active) + { + if (active == null || active.length == 0) + { + // Active for the entire duration of the game. + _lifetime = game.getLifetime(); + _lifetime.register(this); + } else + { + _lifetime = game.getLifetime().register(this, Arrays.asList(active)); + } + } + + @Override + public Lifetime getLifetime() + { + return _lifetime; + } +} diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/gladiators/Gladiators.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/gladiators/Gladiators.java index 59dd19d6c..907c62858 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/gladiators/Gladiators.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/gladiators/Gladiators.java @@ -1115,6 +1115,7 @@ public class Gladiators extends SoloGame @Override public void disable() { + super.disable(); _hotbarEditor.deregisterSelf(); // De-register as listener _hotbarEditor.onDisable(); // Fully disable } diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/WizardSpellMenu.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/WizardSpellMenu.java index 6a384727d..f9f5a9236 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/WizardSpellMenu.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/WizardSpellMenu.java @@ -1,13 +1,14 @@ package nautilus.game.arcade.game.games.wizards; -import mineplex.core.MiniPlugin; import mineplex.core.common.util.C; import mineplex.core.common.util.UtilInv; import mineplex.core.common.util.UtilServer; import mineplex.core.itemstack.ItemBuilder; +import mineplex.core.lifetimes.Lifetime; import nautilus.game.arcade.events.GameStateChangeEvent; import nautilus.game.arcade.game.Game.GameState; +import nautilus.game.arcade.game.GameComponent; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -20,18 +21,17 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.java.JavaPlugin; -public class WizardSpellMenu extends MiniPlugin +public class WizardSpellMenu extends GameComponent { private Wizards _wizards; private WizardSpellMenuShop _wizardShop; private ItemStack _wizardSpells = new ItemBuilder(Material.ENCHANTED_BOOK).setTitle(C.cGold + "Wizard Spells") .addLore(C.cGray + "Right click with this to view the spells").build(); - public WizardSpellMenu(String moduleName, JavaPlugin plugin, Wizards wizards) + public WizardSpellMenu(Wizards wizards) { - super("Wizard Spell Menu", plugin); + super(wizards); _wizardShop = new WizardSpellMenuShop(this, wizards.getArcadeManager().GetClients(), wizards.getArcadeManager() .GetDonation(), wizards); _wizards = wizards; @@ -49,7 +49,7 @@ public class WizardSpellMenu extends MiniPlugin @EventHandler public void onDeath(final PlayerDeathEvent event) { - Bukkit.getScheduler().scheduleSyncDelayedTask(_plugin, new Runnable() + Bukkit.getScheduler().scheduleSyncDelayedTask(UtilServer.getPlugin(), new Runnable() { public void run() { @@ -131,5 +131,4 @@ public class WizardSpellMenu extends MiniPlugin } } } - } diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/Wizards.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/Wizards.java index c4463ea18..5401f66d1 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/Wizards.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/wizards/Wizards.java @@ -153,7 +153,7 @@ public class Wizards extends SoloGame new KitWitchDoctor(manager) }); - _wizard = new WizardSpellMenu("Wizard Spell Menu", getArcadeManager().getPlugin(), this); + _wizard = new WizardSpellMenu(this); AnnounceStay = false; BlockBreak = true; diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameCreationManager.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameCreationManager.java index 39ed239e9..b0c6ae5f9 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameCreationManager.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameCreationManager.java @@ -87,8 +87,7 @@ public class GameCreationManager implements Listener { if (Manager.GetGame().GetState() == GameState.Dead) { - HandlerList.unregisterAll(Manager.GetGame()); - + Manager.GetGame().disable(); //Schedule Cleanup _ended.add(Manager.GetGame()); @@ -105,15 +104,6 @@ public class GameCreationManager implements Listener while (gameIterator.hasNext()) { Game game = gameIterator.next(); - - game.cleanupModules(); - game.cleanupCommands(); - game.disable(); - - HandlerList.unregisterAll(game); - - for (StatTracker tracker : game.getStatTrackers()) - HandlerList.unregisterAll(tracker); TimingManager.start("GameCreationManager - Attempting Removal - " + game.GetName()); @@ -123,8 +113,7 @@ public class GameCreationManager implements Listener gameIterator.remove(); } else - { - + { boolean removedPlayers = false; if (UtilTime.elapsed(game.GetStateTime(), 20000)) { @@ -154,7 +143,7 @@ public class GameCreationManager implements Listener game.WorldData.Uninitialize(); game.WorldData = null; gameIterator.remove(); - + TimingManager.stop("GameCreationManager - Uninit World - " + game.GetName()); }; } @@ -275,7 +264,7 @@ public class GameCreationManager implements Listener TimingManager.stop("DisplayNext"); TimingManager.start("registerEvents"); - UtilServer.getServer().getPluginManager().registerEvents(Manager.GetGame(), Manager.getPlugin()); + Manager.GetGame().getLifetime().start(GameState.Loading); TimingManager.stop("registerEvents"); } diff --git a/Plugins/pom.xml b/Plugins/pom.xml index 137620d6a..dbc3f82c8 100644 --- a/Plugins/pom.xml +++ b/Plugins/pom.xml @@ -134,11 +134,25 @@ 2.8.1 compile + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-library + 1.3 + test + + ${project.name} + ${project.basedir}/test ${project.basedir}/src