diff --git a/.idea/misc.xml b/.idea/misc.xml index 2758df8..e72e973 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,11 @@ + + + + + + diff --git a/api/src/main/java/io/github/sylviameows/flask/api/FlaskAPI.java b/api/src/main/java/io/github/sylviameows/flask/api/FlaskAPI.java index 5d34bc6..d227c8c 100644 --- a/api/src/main/java/io/github/sylviameows/flask/api/FlaskAPI.java +++ b/api/src/main/java/io/github/sylviameows/flask/api/FlaskAPI.java @@ -1,5 +1,6 @@ package io.github.sylviameows.flask.api; +import io.github.sylviameows.flask.api.events.FlaskDispatcher; import io.github.sylviameows.flask.api.manager.PlayerManager; import io.github.sylviameows.flask.api.registry.GameRegistry; import io.github.sylviameows.flask.api.services.MessageService; @@ -14,6 +15,8 @@ public interface FlaskAPI { WorldService getWorldService(); MessageService getMessageService(); + FlaskDispatcher getDispatcher(); + Plugin getPlugin(); Location getSpawnLocation(); diff --git a/api/src/main/java/io/github/sylviameows/flask/api/annotations/FlaskEvent.java b/api/src/main/java/io/github/sylviameows/flask/api/annotations/FlaskEvent.java new file mode 100644 index 0000000..2913758 --- /dev/null +++ b/api/src/main/java/io/github/sylviameows/flask/api/annotations/FlaskEvent.java @@ -0,0 +1,11 @@ +package io.github.sylviameows.flask.api.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface FlaskEvent { +} diff --git a/api/src/main/java/io/github/sylviameows/flask/api/events/FlaskDispatcher.java b/api/src/main/java/io/github/sylviameows/flask/api/events/FlaskDispatcher.java new file mode 100644 index 0000000..7fdda1e --- /dev/null +++ b/api/src/main/java/io/github/sylviameows/flask/api/events/FlaskDispatcher.java @@ -0,0 +1,20 @@ +package io.github.sylviameows.flask.api.events; + +import io.github.sylviameows.flask.api.game.Lobby; +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Method; + +public interface FlaskDispatcher extends EventExecutor, Listener { + record ListenerInfo(Lobby lobby, Method method, FlaskListener listener) {} + + void registerEvent(Lobby lobby, FlaskListener listener); + void unregisterEvent(Lobby lobby, FlaskListener listener); + + @Override + void execute(@NotNull Listener listener, @NotNull Event event) throws EventException; +} diff --git a/api/src/main/java/io/github/sylviameows/flask/api/events/FlaskListener.java b/api/src/main/java/io/github/sylviameows/flask/api/events/FlaskListener.java new file mode 100644 index 0000000..2fc1d36 --- /dev/null +++ b/api/src/main/java/io/github/sylviameows/flask/api/events/FlaskListener.java @@ -0,0 +1,7 @@ +package io.github.sylviameows.flask.api.events; + +/** + * This type of listener will automatically filter out irrelevant events to its target game. + */ +public interface FlaskListener { +} diff --git a/api/src/main/java/io/github/sylviameows/flask/api/game/Lobby.java b/api/src/main/java/io/github/sylviameows/flask/api/game/Lobby.java index f4ef3da..fc40584 100644 --- a/api/src/main/java/io/github/sylviameows/flask/api/game/Lobby.java +++ b/api/src/main/java/io/github/sylviameows/flask/api/game/Lobby.java @@ -5,14 +5,13 @@ import org.bukkit.GameMode; import org.bukkit.World; import org.bukkit.entity.Player; -import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; -public class Lobby { +public class Lobby> { protected final G parent; public List players; @@ -21,36 +20,32 @@ public class Lobby { private World world; public Lobby(G parent) { - this.parent = parent; - this.players = new ArrayList<>(); - - this.phase = parent.initialPhase(); - Bukkit.getPluginManager().registerEvents(this.phase, parent.getPlugin()); - this.phase.onEnabled(this); + this(parent, new ArrayList<>()); } public Lobby(G parent, List players) { this.parent = parent; this.players = players; - var api = parent.getPlugin().getFlaskAPI(); + FlaskAPI api = parent.getPlugin().getFlaskAPI(); players.forEach(player -> api.getPlayerManager().get(player).setLobby(this)); this.phase = parent.initialPhase(); - Bukkit.getPluginManager().registerEvents(this.phase, parent.getPlugin()); + api.getPlugin().getLogger().info("registering"); + api.getDispatcher().registerEvent(this, this.phase); this.phase.onEnabled(this); } // todo: call function in phase public void addPlayer(Player player) { - var api = parent.getPlugin().getFlaskAPI(); + FlaskAPI api = parent.getPlugin().getFlaskAPI(); api.getPlayerManager().get(player).setLobby(this); players.add(player); phase.onPlayerJoin(player); } public void removePlayer(Player player) { - var api = parent.getPlugin().getFlaskAPI(); + FlaskAPI api = parent.getPlugin().getFlaskAPI(); api.getPlayerManager().get(player).setLobby(null); players.remove(player); phase.onPlayerLeave(player); @@ -58,7 +53,9 @@ public void removePlayer(Player player) { public void closeLobby() { phase.onDisabled(); - HandlerList.unregisterAll(phase); + + FlaskAPI.instance().getPlugin().getLogger().info("unregistering"); + FlaskAPI.instance().getDispatcher().unregisterEvent(this, phase); // todo: replace with a requeue feature? players.forEach(player -> { @@ -86,12 +83,14 @@ public Phase getPhase() { } public void updatePhase(@NotNull Phase phase) { this.phase.onDisabled(); - HandlerList.unregisterAll(this.phase); + FlaskAPI.instance().getPlugin().getLogger().info("unregistering"); + FlaskAPI.instance().getDispatcher().unregisterEvent(this, phase); this.phase = phase; this.phase.onEnabled(this); - Bukkit.getPluginManager().registerEvents(this.phase, parent.getPlugin()); + FlaskAPI.instance().getPlugin().getLogger().info("registering"); + FlaskAPI.instance().getDispatcher().registerEvent(this, phase); } public void nextPhase() { diff --git a/api/src/main/java/io/github/sylviameows/flask/api/game/Phase.java b/api/src/main/java/io/github/sylviameows/flask/api/game/Phase.java index 753bf3d..fa75004 100644 --- a/api/src/main/java/io/github/sylviameows/flask/api/game/Phase.java +++ b/api/src/main/java/io/github/sylviameows/flask/api/game/Phase.java @@ -1,9 +1,10 @@ package io.github.sylviameows.flask.api.game; +import io.github.sylviameows.flask.api.events.FlaskListener; import org.bukkit.entity.Player; import org.bukkit.event.Listener; -public interface Phase extends Listener { +public interface Phase extends FlaskListener { /* TODO */ void onEnabled(Lobby parent); diff --git a/build.gradle.kts b/build.gradle.kts index b3800e6..d06b550 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ allprojects { group = "io.github.sylviameows" - version = "0.9.0-ALPHA" + version = "0.10.0-ALPHA" } \ No newline at end of file diff --git a/core/src/main/java/io/github/sylviameows/flask/Flask.java b/core/src/main/java/io/github/sylviameows/flask/Flask.java index 3b3464a..80163dd 100644 --- a/core/src/main/java/io/github/sylviameows/flask/Flask.java +++ b/core/src/main/java/io/github/sylviameows/flask/Flask.java @@ -2,6 +2,7 @@ import io.github.sylviameows.flask.api.FlaskAPI; import io.github.sylviameows.flask.api.FlaskPlugin; +import io.github.sylviameows.flask.api.events.FlaskDispatcher; import io.github.sylviameows.flask.api.manager.PlayerManager; import io.github.sylviameows.flask.api.registry.GameRegistry; import io.github.sylviameows.flask.api.services.MessageService; @@ -11,6 +12,7 @@ import io.github.sylviameows.flask.commands.hologram.HologramCommand; import io.github.sylviameows.flask.commands.queue.QueueCommand; import io.github.sylviameows.flask.hub.holograms.GameHologram; +import io.github.sylviameows.flask.listeners.FlaskDispatcherImpl; import io.github.sylviameows.flask.listeners.JoinListener; import io.github.sylviameows.flask.listeners.LeaveListener; import io.github.sylviameows.flask.listeners.RightClickEntity; @@ -44,6 +46,7 @@ public class Flask extends FlaskPlugin implements FlaskAPI { private static MessageServiceImpl messageService; private static WorldService worldService; private static Flask instance; + private static FlaskDispatcherImpl dispatcher; @Override public void onEnable() { @@ -60,8 +63,10 @@ public void onEnable() { JoinListener.register(this); LeaveListener.register(this); + Flask.dispatcher = new FlaskDispatcherImpl(); Flask.messageService = new MessageServiceImpl(this); Flask.worldService = new FileWorldService(); + // commands registerCommands(); @@ -156,6 +161,11 @@ public MessageService getMessageService() { return messageService; } + @Override + public FlaskDispatcher getDispatcher() { + return dispatcher; + } + @Override public Plugin getPlugin() { return this; diff --git a/core/src/main/java/io/github/sylviameows/flask/editor/book/components/variable/BooleanOption.java b/core/src/main/java/io/github/sylviameows/flask/editor/book/components/variable/BooleanOption.java index fe0dcc7..542e06e 100644 --- a/core/src/main/java/io/github/sylviameows/flask/editor/book/components/variable/BooleanOption.java +++ b/core/src/main/java/io/github/sylviameows/flask/editor/book/components/variable/BooleanOption.java @@ -44,7 +44,7 @@ protected Component value() { var component = Component.text(value ? "True" : "False").color(Palette.DARK_GRAY); if (isOptional()) { var d = (boolean) getDefault(); - if (value == d) return component.append(Component.text("(default)").color(Palette.GRAY)); + if (value == d) return component.append(Component.text(" (default)").color(Palette.GRAY)); } return component; diff --git a/core/src/main/java/io/github/sylviameows/flask/listeners/FlaskDispatcherImpl.java b/core/src/main/java/io/github/sylviameows/flask/listeners/FlaskDispatcherImpl.java new file mode 100644 index 0000000..597d2de --- /dev/null +++ b/core/src/main/java/io/github/sylviameows/flask/listeners/FlaskDispatcherImpl.java @@ -0,0 +1,134 @@ +package io.github.sylviameows.flask.listeners; + +import io.github.sylviameows.flask.Flask; +import io.github.sylviameows.flask.api.FlaskPlayer; +import io.github.sylviameows.flask.api.annotations.FlaskEvent; +import io.github.sylviameows.flask.api.events.FlaskDispatcher; +import io.github.sylviameows.flask.api.events.FlaskListener; +import io.github.sylviameows.flask.api.game.Lobby; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.*; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.event.vehicle.VehicleEvent; +import org.bukkit.event.world.WorldEvent; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.*; + +public class FlaskDispatcherImpl implements FlaskDispatcher { + Map, ArrayList> methodMap = new HashMap<>(); + + @Override + public void registerEvent(Lobby lobby, FlaskListener listener) { + for (Method method : listener.getClass().getDeclaredMethods()) { + if (method.isAnnotationPresent(FlaskEvent.class)) { + Parameter[] parameters = method.getParameters(); + if (parameters.length == 1) { + Class clazz = parameters[0].getType(); + + if (Event.class.isAssignableFrom(clazz)) { + //noinspection unchecked + Class eventClass = (Class) clazz; + method.setAccessible(true); + + methodMap.computeIfAbsent(eventClass, k -> { + Bukkit.getPluginManager().registerEvent( + eventClass, + this, + EventPriority.HIGH, + this, + Flask.getInstance() + ); + + return new ArrayList<>(); + }).add(new ListenerInfo(lobby, method, listener)); + } + } + } + } + } + + @Override + public void unregisterEvent(Lobby lobby, FlaskListener listener) { + methodMap.forEach((clazz, listeners) -> { + Optional optional = listeners.stream().filter(info -> (info.listener() == listener && info.lobby() == lobby)).findFirst(); + if (optional.isEmpty()) return; + ListenerInfo info = optional.get(); + + listeners.remove(info); + if (listeners.isEmpty()) { + methodMap.remove(clazz); + // todo: find a way to unregister event fully + } + }); + } + + @Override + public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { + ArrayList listeners = new ArrayList<>(); + + Class clazz = event.getClass(); + methodMap.forEach((e, i) -> {if (e.isAssignableFrom(clazz)) listeners.addAll(i);}); + if (listeners.isEmpty()) return; + + ListenerInfo info = null; + switch (event) { + case PlayerEvent playerEvent -> { + Player player = playerEvent.getPlayer(); + FlaskPlayer fp = Flask.getInstance().getPlayerManager().get(player); + + Lobby lobby = fp.getLobby(); + for (ListenerInfo i : listeners) { + if (i.lobby() == lobby) { + info = i; + break; + } + } + } + case EntityEvent entityEvent -> { + Entity entity = entityEvent.getEntity(); + World world = entity.getWorld(); + info = findListenerMatchingWorld(listeners, world); + } + case WorldEvent worldEvent -> { + World world = worldEvent.getWorld(); + info = findListenerMatchingWorld(listeners, world); + } + case BlockEvent blockEvent -> { + World world = blockEvent.getBlock().getWorld(); + info = findListenerMatchingWorld(listeners, world); + } + case VehicleEvent vehicleEvent -> { + World world = vehicleEvent.getVehicle().getWorld(); + info = findListenerMatchingWorld(listeners, world); + } + default -> { + } + } + + if (info == null) return; + + try { + info.method().invoke(info.listener(), event); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new EventException(e); + } + } + + private ListenerInfo findListenerMatchingWorld(ArrayList listeners, World world) { + for (ListenerInfo info : listeners) { + if (info.lobby().getWorld().getName().equals(world.getName())) { + return info; + } + } + return null; + } +} diff --git a/example/src/main/java/io/github/sylviameows/duels/Duels.java b/example/src/main/java/io/github/sylviameows/duels/Duels.java index 5ec9cb8..992e4a5 100644 --- a/example/src/main/java/io/github/sylviameows/duels/Duels.java +++ b/example/src/main/java/io/github/sylviameows/duels/Duels.java @@ -3,8 +3,11 @@ import io.github.sylviameows.duels.basic.ExampleGame; import io.github.sylviameows.flask.api.FlaskAPI; import io.github.sylviameows.flask.api.FlaskPlugin; +import io.github.sylviameows.flask.api.game.Lobby; import org.bukkit.Bukkit; +import java.util.ArrayList; + public final class Duels extends FlaskPlugin { private FlaskAPI flask; @@ -16,8 +19,12 @@ public void onEnable() { flask = api; } - new ExampleGame(this).register("sword"); + new ExampleGame(this).register("duel"); + + TestingGame eg = new TestingGame(this); + eg.register("test"); + eg.createLobby(new ArrayList<>()); } @Override diff --git a/example/src/main/java/io/github/sylviameows/duels/TestingGame.java b/example/src/main/java/io/github/sylviameows/duels/TestingGame.java new file mode 100644 index 0000000..2ca7a7d --- /dev/null +++ b/example/src/main/java/io/github/sylviameows/duels/TestingGame.java @@ -0,0 +1,38 @@ +package io.github.sylviameows.duels; + +import io.github.sylviameows.flask.api.FlaskPlugin; +import io.github.sylviameows.flask.api.annotations.GameProperties; +import io.github.sylviameows.flask.api.game.Game; +import io.github.sylviameows.flask.api.game.Lobby; +import io.github.sylviameows.flask.api.game.Phase; +import io.github.sylviameows.flask.api.game.map.MapManager; +import io.github.sylviameows.flask.api.map.FlaskMap; +import org.bukkit.entity.Player; + +import java.util.List; + +@GameProperties( + name="testing", + min=1, + max=2 +) +public class TestingGame extends Game { + protected TestingGame(FlaskPlugin plugin) { + super(plugin); + } + + @Override + public Lobby createLobby(List players) { + return new Lobby(this); + } + + @Override + public Phase initialPhase() { + return new TestingPhase(); + } + + @Override + public MapManager getMapManager() { + return null; + } +} diff --git a/example/src/main/java/io/github/sylviameows/duels/TestingPhase.java b/example/src/main/java/io/github/sylviameows/duels/TestingPhase.java new file mode 100644 index 0000000..1c75fb6 --- /dev/null +++ b/example/src/main/java/io/github/sylviameows/duels/TestingPhase.java @@ -0,0 +1,29 @@ +package io.github.sylviameows.duels; + +import io.github.sylviameows.flask.api.annotations.FlaskEvent; +import io.github.sylviameows.flask.api.game.Lobby; +import io.github.sylviameows.flask.api.game.Phase; +import org.bukkit.event.player.PlayerDropItemEvent; + +public class TestingPhase implements Phase { + @FlaskEvent + public void dropItem(PlayerDropItemEvent event) { + event.setCancelled(true); + } + + @Override + public void onEnabled(Lobby parent) { + + } + + @Override + public void onDisabled() { + + } + + @Override + public Phase next() { + return null; + } + +} diff --git a/example/src/main/java/io/github/sylviameows/duels/basic/ExampleEndingPhase.java b/example/src/main/java/io/github/sylviameows/duels/basic/ExampleEndingPhase.java index 3245022..8f4adad 100644 --- a/example/src/main/java/io/github/sylviameows/duels/basic/ExampleEndingPhase.java +++ b/example/src/main/java/io/github/sylviameows/duels/basic/ExampleEndingPhase.java @@ -1,6 +1,7 @@ package io.github.sylviameows.duels.basic; import io.github.sylviameows.flask.api.Palette; +import io.github.sylviameows.flask.api.annotations.FlaskEvent; import io.github.sylviameows.flask.api.game.Lobby; import io.github.sylviameows.flask.api.game.Phase; import net.kyori.adventure.key.Key; @@ -12,7 +13,6 @@ import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityDamageEvent; import java.time.Duration; @@ -86,12 +86,10 @@ public void onDisabled() { } } - @EventHandler - private void damage(EntityDamageEvent event) { - if (event.getEntity() instanceof Player player) { - if (parent.players.contains(player)) { - event.setCancelled(true); - } + @FlaskEvent + public void damage(EntityDamageEvent event) { + if (event.getEntity() instanceof Player) { + event.setCancelled(true); } } diff --git a/example/src/main/java/io/github/sylviameows/duels/basic/ExamplePlayingPhase.java b/example/src/main/java/io/github/sylviameows/duels/basic/ExamplePlayingPhase.java index 3b282c4..9ebe800 100644 --- a/example/src/main/java/io/github/sylviameows/duels/basic/ExamplePlayingPhase.java +++ b/example/src/main/java/io/github/sylviameows/duels/basic/ExamplePlayingPhase.java @@ -1,11 +1,14 @@ package io.github.sylviameows.duels.basic; +import io.github.sylviameows.flask.api.annotations.FlaskEvent; import io.github.sylviameows.flask.api.game.Lobby; import io.github.sylviameows.flask.api.game.Phase; import org.bukkit.GameMode; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerDropItemEvent; import org.jetbrains.annotations.NotNull; public class ExamplePlayingPhase implements Phase { @@ -39,8 +42,8 @@ public void onPlayerLeave(Player player) { parent.nextPhase(); } - @EventHandler - private void onDeath(EntityDeathEvent event) { + @FlaskEvent + public void onDeath(EntityDeathEvent event) { if (event.getEntity() instanceof Player player) { if (player == playerA) { nextPhase.setWinner(playerB);