From 50b44e46e04624b5ad6a60e9f833127d15f7ed1e Mon Sep 17 00:00:00 2001 From: ThatCreeper Date: Wed, 30 Dec 2020 17:12:03 -0600 Subject: [PATCH] Add PotionEffectManager --- .../net/minestom/server/MinecraftServer.java | 13 ++ .../listener/PlayerDiggingListener.java | 13 +- .../server/play/EntityEffectPacket.java | 15 +- .../net/minestom/server/potion/Potion.java | 58 ++++++ .../server/potion/PotionEffectManager.java | 185 ++++++++++++++++++ .../minestom/server/potion/PotionTask.java | 25 +++ .../server/potion/PotionTimeTask.java | 27 +++ .../server/potion/PotionTimeTaskHolder.java | 12 ++ src/test/java/demo/Main.java | 1 + src/test/java/demo/PlayerInit.java | 1 + .../java/demo/commands/PotionCommand.java | 53 +++++ 11 files changed, 390 insertions(+), 13 deletions(-) create mode 100644 src/main/java/net/minestom/server/potion/Potion.java create mode 100644 src/main/java/net/minestom/server/potion/PotionEffectManager.java create mode 100644 src/main/java/net/minestom/server/potion/PotionTask.java create mode 100644 src/main/java/net/minestom/server/potion/PotionTimeTask.java create mode 100644 src/main/java/net/minestom/server/potion/PotionTimeTaskHolder.java create mode 100644 src/test/java/demo/commands/PotionCommand.java diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 1cf608505..41d446fbd 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -33,6 +33,7 @@ import net.minestom.server.network.packet.server.play.UpdateViewDistancePacket; import net.minestom.server.particle.Particle; import net.minestom.server.ping.ResponseDataConsumer; import net.minestom.server.potion.PotionEffect; +import net.minestom.server.potion.PotionEffectManager; import net.minestom.server.potion.PotionType; import net.minestom.server.recipe.RecipeManager; import net.minestom.server.registry.ResourceGatherer; @@ -116,6 +117,7 @@ public final class MinecraftServer { private static DimensionTypeManager dimensionTypeManager; private static BiomeManager biomeManager; private static AdvancementManager advancementManager; + private static PotionEffectManager potionEffectManager; private static ExtensionManager extensionManager; @@ -177,6 +179,7 @@ public final class MinecraftServer { dimensionTypeManager = new DimensionTypeManager(); biomeManager = new BiomeManager(); advancementManager = new AdvancementManager(); + potionEffectManager = new PotionEffectManager(); updateManager = new UpdateManager(); @@ -621,6 +624,16 @@ public final class MinecraftServer { return advancementManager; } + /** + * Gets the manager handling potions. + * + * @return the potion effect manager + */ + public static PotionEffectManager getPotionEffectManager() { + checkInitStatus(potionEffectManager); + return potionEffectManager; + } + /** * Get the manager handling {@link Extension}. * diff --git a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java index 3fe064765..49d60dcb8 100644 --- a/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java +++ b/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java @@ -14,6 +14,7 @@ import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket; import net.minestom.server.network.packet.server.play.AcknowledgePlayerDiggingPacket; import net.minestom.server.network.packet.server.play.EntityEffectPacket; import net.minestom.server.network.packet.server.play.RemoveEntityEffectPacket; +import net.minestom.server.potion.Potion; import net.minestom.server.potion.PotionEffect; import net.minestom.server.utils.BlockPosition; @@ -189,10 +190,14 @@ public class PlayerDiggingListener { EntityEffectPacket entityEffectPacket = new EntityEffectPacket(); entityEffectPacket.entityId = player.getEntityId(); - entityEffectPacket.effect = PotionEffect.MINING_FATIGUE; - entityEffectPacket.amplifier = -1; - entityEffectPacket.duration = 0; - entityEffectPacket.flags = 0; + entityEffectPacket.potion = new Potion( + PotionEffect.MINING_FATIGUE, + (byte) -1, + 0, + false, + false, + false + ); player.getPlayerConnection().sendPacket(entityEffectPacket); } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java index 596faced4..b9fbaacaf 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntityEffectPacket.java @@ -2,25 +2,22 @@ package net.minestom.server.network.packet.server.play; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import net.minestom.server.potion.PotionEffect; +import net.minestom.server.potion.Potion; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; public class EntityEffectPacket implements ServerPacket { public int entityId; - public PotionEffect effect; - public byte amplifier; - public int duration; - public byte flags; + public Potion potion; @Override public void write(@NotNull BinaryWriter writer) { writer.writeVarInt(entityId); - writer.writeByte((byte) effect.getId()); - writer.writeByte(amplifier); - writer.writeVarInt(duration); - writer.writeByte(flags); + writer.writeByte((byte) potion.effect.getId()); + writer.writeByte(potion.amplifier); + writer.writeVarInt(potion.duration); + writer.writeByte(potion.getFlags()); } @Override diff --git a/src/main/java/net/minestom/server/potion/Potion.java b/src/main/java/net/minestom/server/potion/Potion.java new file mode 100644 index 000000000..e86cc10ab --- /dev/null +++ b/src/main/java/net/minestom/server/potion/Potion.java @@ -0,0 +1,58 @@ +package net.minestom.server.potion; + +import net.minestom.server.entity.Player; +import net.minestom.server.network.packet.server.play.EntityEffectPacket; +import net.minestom.server.network.packet.server.play.RemoveEntityEffectPacket; +import org.jetbrains.annotations.NotNull; + +public class Potion { + public PotionEffect effect; + public byte amplifier; + public int duration; + public boolean ambient; + public boolean particles; + public boolean icon; + + public Potion(PotionEffect effect, byte amplifier, int duration) { + this(effect, amplifier, duration, true, true, false); + } + + public Potion(PotionEffect effect, byte amplifier, int duration, boolean particles) { + this(effect, amplifier, duration, particles, true, false); + } + + public Potion(PotionEffect effect, byte amplifier, int duration, boolean particles, boolean icon) { + this(effect, amplifier, duration, particles, icon, false); + } + + public Potion(PotionEffect effect, byte amplifier, int duration, boolean particles, boolean icon, boolean ambient) { + this.effect = effect; + this.amplifier = amplifier; + this.duration = duration; + this.particles = particles; + this.icon = icon; + this.ambient = ambient; + } + + public byte getFlags() { + byte computed = 0x00; + if (ambient) computed = (byte)(computed | 0x01); + if (particles) computed = (byte)(computed | 0x02); + if (icon) computed = (byte)(computed | 0x04); + return computed; + } + + public void sendAddPacket(@NotNull Player player) { + EntityEffectPacket eep = new EntityEffectPacket(); + eep.entityId = player.getEntityId(); + eep.potion = this; + player.sendPacketToViewersAndSelf(eep); + } + + public void sendRemovePacket(@NotNull Player player) { + RemoveEntityEffectPacket reep = new RemoveEntityEffectPacket(); + reep.entityId = player.getEntityId(); + reep.effect = effect; + player.sendPacketToViewersAndSelf(reep); + } +} diff --git a/src/main/java/net/minestom/server/potion/PotionEffectManager.java b/src/main/java/net/minestom/server/potion/PotionEffectManager.java new file mode 100644 index 000000000..bb4d47533 --- /dev/null +++ b/src/main/java/net/minestom/server/potion/PotionEffectManager.java @@ -0,0 +1,185 @@ +package net.minestom.server.potion; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.Player; +import net.minestom.server.event.GlobalEventHandler; +import net.minestom.server.event.player.PlayerDisconnectEvent; +import net.minestom.server.event.player.PlayerSpawnEvent; +import net.minestom.server.timer.SchedulerManager; +import net.minestom.server.utils.time.TimeUnit; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +/** + * Manages active potion effects on players + */ +public class PotionEffectManager { + + private HashMap> playerEffects; + private boolean persistEffects = false; + + /** + * Creates a new PotionEffectManager + */ + public PotionEffectManager() { + playerEffects = new HashMap<>(); + + addEventHandlers(); + } + + /** + * Returns the active effects of a player. + * + * @param player The player to get the effects of. + * @return Null if no active effects, otherwise the active effects. + */ + public @Nullable List getActiveEffects(@Nullable Player player) { + if (player == null) return null; + return playerEffects.get(player.getUuid()); + } + + /** + * Returns a {@link PotionTask} on a player of a type {@link PotionEffect} + * + * @param player The player with the effect. + * @param potionEffect The type of potion to look for. + * @return Null if the player does not have the effect, otherwise the effect. + */ + public @Nullable PotionTask getPotionTask(@Nullable Player player, @Nullable PotionEffect potionEffect) { + if (player == null) return null; + if (potionEffect == null) return null; + + ArrayList potionTasks = playerEffects.get(player.getUuid()); + if (potionTasks == null) return null; + + for (PotionTask potionTask : potionTasks) { + if (potionTask.getPotion().effect == potionEffect) { + return potionTask; + } + } + return null; + } + + /** + * Returns if the player has an effect of specified type. + * + * @param player The player to check. + * @param potionEffect The type of potion to check for. + * @return If the player has a potion effect with the specified type. + */ + public boolean hasPotionEffect(@Nullable Player player, @Nullable PotionEffect potionEffect) { + return getPotionTask(player, potionEffect) != null; + } + + /** + * Removes the {@link PotionTask}. + * + * @param task The {@link PotionTask} to remove. + */ + public void removeEffect(@NotNull PotionTask task) { + task.getValue().task.cancel(); + for (UUID uuid : playerEffects.keySet()) { + if (playerEffects.get(uuid).contains(task)) { + playerEffects.get(uuid).remove(task); + Player player = MinecraftServer.getConnectionManager().getPlayer(uuid); + if (player != null && player.isOnline()) { + task.getPotion().sendRemovePacket(player); + } + return; + } + } + } + + /** + * If the player has a potion of type effect, remove it. + * + * @param player The player to remove from. + * @param effect The {@link PotionEffect} to remove. + */ + public void removeEffect(@NotNull Player player, @NotNull PotionEffect effect) { + PotionTask task = null; + ArrayList potionTasks = playerEffects.get(player.getUuid()); + if (potionTasks == null) return; + for (PotionTask potionTask : potionTasks) { + if (potionTask.getPotion().effect == effect) { + task = potionTask; + break; + } + } + if (task == null) return; + removeEffect(task); + } + + /** + * Clears a player's effects. + * + * @param player The player to clear. + */ + public void clearEffects(@Nullable Player player) { + if (player == null) return; + + ArrayList potionTasks = playerEffects.get(player.getUuid()); + if (potionTasks == null) return; + for (PotionTask potionTask : potionTasks) { + potionTask.getValue().task.cancel(); + if (player.isOnline()) { + potionTask.getPotion().sendRemovePacket(player); + } + } + potionTasks.clear(); + playerEffects.remove(player.getUuid()); + } + + /** + * Gives a player a potion effect. + * + * @param player The player to give to. + * @param potion The {@link Potion} to give. + */ + public void addPotion(@Nullable Player player, @Nullable Potion potion) { + if (player == null) return; + if (potion == null) return; + removeEffect(player, potion.effect); + ArrayList potionTasks = playerEffects.computeIfAbsent(player.getUuid(), k -> new ArrayList<>()); + SchedulerManager schedulerManager = MinecraftServer.getSchedulerManager(); + PotionTimeTask ptt = new PotionTimeTask(MinecraftServer.getSchedulerManager(), + TimeUnit.TICK.toMilliseconds(potion.duration)); + potionTasks.add(new PotionTask(potion, ptt)); + potion.sendAddPacket(player); + } + +// NOT IMPLEMENTED +// public void setPersistEffects(Boolean persistEffects) { +// this.persistEffects = persistEffects; +// } +// +// public boolean getPersistEffects() { +// return persistEffects; +// } + + /** + * Adds a {@link PlayerSpawnEvent} and {@link PlayerDisconnectEvent} handler to pause and resume potion effects. + */ + private void addEventHandlers() { + GlobalEventHandler globalEventHandler = MinecraftServer.getGlobalEventHandler(); + globalEventHandler.addEventCallback(PlayerSpawnEvent.class, playerSpawnEvent -> { + if (persistEffects) { + // TODO: Implement persist + } + }); + + globalEventHandler.addEventCallback(PlayerDisconnectEvent.class, playerDisconnectEvent -> { + if (persistEffects) { + // TODO: Implement persist + } else { + clearEffects(playerDisconnectEvent.getPlayer()); + } + }); + } + +} diff --git a/src/main/java/net/minestom/server/potion/PotionTask.java b/src/main/java/net/minestom/server/potion/PotionTask.java new file mode 100644 index 000000000..466fd3a9a --- /dev/null +++ b/src/main/java/net/minestom/server/potion/PotionTask.java @@ -0,0 +1,25 @@ +package net.minestom.server.potion; + +import javafx.util.Pair; +import net.minestom.server.MinecraftServer; + +public class PotionTask extends Pair { + /** + * Creates a new PotionTask + * + * @param potion The {@link Potion} + * @param task The {@link PotionTimeTask} + */ + public PotionTask(Potion potion, PotionTimeTask task) { + super(potion, task); + task.potionTask = this; + } + + public Potion getPotion() { + return this.getKey(); + } + + public void removeEffect() { + MinecraftServer.getPotionEffectManager().removeEffect(this); + } +} diff --git a/src/main/java/net/minestom/server/potion/PotionTimeTask.java b/src/main/java/net/minestom/server/potion/PotionTimeTask.java new file mode 100644 index 000000000..fcabf442c --- /dev/null +++ b/src/main/java/net/minestom/server/potion/PotionTimeTask.java @@ -0,0 +1,27 @@ +package net.minestom.server.potion; + +import net.minestom.server.timer.SchedulerManager; +import net.minestom.server.timer.Task; +import net.minestom.server.utils.time.TimeUnit; +import org.jetbrains.annotations.NotNull; + +public class PotionTimeTask { + + public Long remainingTime; + public Task task; + public PotionTask potionTask; + + /** + * Creates a task. + * + * @param schedulerManager The manager for the task + * @param delay The time to delay + */ + public PotionTimeTask(@NotNull SchedulerManager schedulerManager, long delay) { + Runnable runTask = () -> { + potionTask.removeEffect(); + }; + task = schedulerManager.buildTask(runTask).delay(delay, TimeUnit.MILLISECOND).schedule(); + this.remainingTime = delay; + } +} diff --git a/src/main/java/net/minestom/server/potion/PotionTimeTaskHolder.java b/src/main/java/net/minestom/server/potion/PotionTimeTaskHolder.java new file mode 100644 index 000000000..b833a8c9c --- /dev/null +++ b/src/main/java/net/minestom/server/potion/PotionTimeTaskHolder.java @@ -0,0 +1,12 @@ +package net.minestom.server.potion; + +public class PotionTimeTaskHolder { + /** + * Only updates when task is paused + */ + public Long timeRemaining; + + public PotionTimeTaskHolder() { + // TODO: Implement persist + } +} diff --git a/src/test/java/demo/Main.java b/src/test/java/demo/Main.java index 0b47ed282..fc0e94393 100644 --- a/src/test/java/demo/Main.java +++ b/src/test/java/demo/Main.java @@ -39,6 +39,7 @@ public class Main { commandManager.register(new ShutdownCommand()); commandManager.register(new TeleportCommand()); commandManager.register(new PlayersCommand()); + commandManager.register(new PotionCommand()); commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage("unknown command")); diff --git a/src/test/java/demo/PlayerInit.java b/src/test/java/demo/PlayerInit.java index 22f5c8c7e..1501e54bd 100644 --- a/src/test/java/demo/PlayerInit.java +++ b/src/test/java/demo/PlayerInit.java @@ -29,6 +29,7 @@ import net.minestom.server.item.Material; import net.minestom.server.network.ConnectionManager; import net.minestom.server.network.packet.server.play.PlayerListHeaderAndFooterPacket; import net.minestom.server.ping.ResponseDataConsumer; +import net.minestom.server.timer.TaskBuilder; import net.minestom.server.utils.PacketUtils; import net.minestom.server.utils.Position; import net.minestom.server.utils.Vector; diff --git a/src/test/java/demo/commands/PotionCommand.java b/src/test/java/demo/commands/PotionCommand.java new file mode 100644 index 000000000..64451e1a5 --- /dev/null +++ b/src/test/java/demo/commands/PotionCommand.java @@ -0,0 +1,53 @@ +package demo.commands; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandSender; +import net.minestom.server.command.builder.Arguments; +import net.minestom.server.command.builder.Command; +import net.minestom.server.command.builder.arguments.Argument; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.entity.Player; +import net.minestom.server.potion.Potion; +import net.minestom.server.potion.PotionEffect; +import net.minestom.server.potion.PotionTask; + +import java.util.List; + +public class PotionCommand extends Command { + + public PotionCommand() { + super("potion"); + + setCondition(this::condition); + + setDefaultExecutor(((sender, args) -> { + sender.sendMessage("Usage: /potion [type] [duration (seconds)]"); + })); + + Argument potionArg = ArgumentType.Potion("potion"); + Argument durationArg = ArgumentType.Integer("duration"); + + addSyntax(this::onPotionCommand, potionArg, durationArg); + } + + private boolean condition(CommandSender sender, String commandString) { + if (!sender.isPlayer()) { + sender.sendMessage("The command is only available for players"); + return false; + } + return true; + } + + private void onPotionCommand(CommandSender sender, Arguments args) { + final Player player = (Player) sender; + final PotionEffect potion = args.getPotionEffect("potion"); + final int duration = args.getInteger("duration"); + + MinecraftServer.getPotionEffectManager().addPotion(player, new Potion( + potion, + (byte) 1, + duration * 20 + )); + } + +} \ No newline at end of file