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).
+ *
+ * - Lifetime started with a value of A
+ * - Component is activated
+ * - setPhase is called with a value of A
+ * - Phase is set to B
+ * - setPhase is called with a value of B
+ * - Phase is set to C
+ * - setPhase is called with a value of C
+ * - Component is deactivated
+ * - Phase is set to D
+ * - Phase is set to E
+ * - Component is activated
+ * - setPhase is called with a value of E
+ * - Phase is set to F
+ * - setPhase is called with a value of F
+ * - Component is deactivated
+ * - Lifetime ends
+ *
+ *
+ * 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