diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 1a34654f..1ccdef15 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -32,6 +32,7 @@ object Versions { const val PACKETEVENTS = "2.11.1" const val WORLDGUARD = "7.0.15-beta-01" const val LUCKPERMS = "5.5.17" + const val ETERNALCORE = "2.0.1-SNAPSHOT+12" } diff --git a/eternalcombat-plugin/build.gradle.kts b/eternalcombat-plugin/build.gradle.kts index fb3a8d48..ecefb955 100644 --- a/eternalcombat-plugin/build.gradle.kts +++ b/eternalcombat-plugin/build.gradle.kts @@ -1,6 +1,6 @@ -import net.minecrell.pluginyml.bukkit.BukkitPluginDescription + import io.papermc.hangarpublishplugin.model.Platforms -import org.gradle.kotlin.dsl.shadowJar +import net.minecrell.pluginyml.bukkit.BukkitPluginDescription plugins { `eternalcombat-java` @@ -93,11 +93,14 @@ bukkit { tasks { runServer { - minecraftVersion("1.21.10") - downloadPlugins.modrinth("WorldEdit", Versions.WORLDEDIT) - downloadPlugins.modrinth("PacketEvents", "${Versions.PACKETEVENTS}+spigot") - downloadPlugins.modrinth("WorldGuard", Versions.WORLDGUARD) - downloadPlugins.modrinth("LuckPerms", "v${Versions.LUCKPERMS}-bukkit") + minecraftVersion("1.21.11") + downloadPlugins { + modrinth("WorldEdit", Versions.WORLDEDIT) + modrinth("PacketEvents", "${Versions.PACKETEVENTS}+spigot") + modrinth("WorldGuard", Versions.WORLDGUARD) + modrinth("LuckPerms", "v${Versions.LUCKPERMS}-bukkit") + modrinth("EternalCore", Versions.ETERNALCORE) + } } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java index 4b492fc1..832da020 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java @@ -1,54 +1,55 @@ package com.eternalcode.combat; -import com.eternalcode.combat.border.BorderTriggerController; import com.eternalcode.combat.border.BorderService; import com.eternalcode.combat.border.BorderServiceImpl; +import com.eternalcode.combat.border.BorderTriggerController; import com.eternalcode.combat.border.animation.block.BorderBlockController; import com.eternalcode.combat.border.animation.particle.ParticleController; import com.eternalcode.combat.bridge.BridgeService; -import com.eternalcode.combat.crystalpvp.RespawnAnchorListener; +import com.eternalcode.combat.config.ConfigService; +import com.eternalcode.combat.config.implementation.PluginConfig; import com.eternalcode.combat.crystalpvp.EndCrystalListener; +import com.eternalcode.combat.crystalpvp.RespawnAnchorListener; +import com.eternalcode.combat.event.EventManager; +import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.FightManagerImpl; +import com.eternalcode.combat.fight.FightTagCommand; +import com.eternalcode.combat.fight.FightTask; +import com.eternalcode.combat.fight.controller.FightActionBlockerController; import com.eternalcode.combat.fight.controller.FightBypassAdminController; import com.eternalcode.combat.fight.controller.FightBypassCreativeController; import com.eternalcode.combat.fight.controller.FightBypassPermissionController; import com.eternalcode.combat.fight.controller.FightInventoryController; +import com.eternalcode.combat.fight.controller.FightMessageController; +import com.eternalcode.combat.fight.controller.FightTagController; +import com.eternalcode.combat.fight.controller.FightUnTagController; +import com.eternalcode.combat.fight.death.DeathCommandController; import com.eternalcode.combat.fight.death.DeathEffectController; -import com.eternalcode.combat.fight.drop.DropKeepInventoryService; -import com.eternalcode.combat.fight.FightManager; -import com.eternalcode.combat.fight.drop.DropService; -import com.eternalcode.combat.fight.effect.FightEffectService; -import com.eternalcode.combat.fight.firework.FireworkController; -import com.eternalcode.combat.fight.knockback.KnockbackService; -import com.eternalcode.combat.fight.tagout.FightTagOutService; -import com.eternalcode.combat.fight.pearl.FightPearlService; -import com.eternalcode.combat.handler.InvalidUsageHandlerImpl; -import com.eternalcode.combat.handler.MissingPermissionHandlerImpl; -import com.eternalcode.combat.config.ConfigService; -import com.eternalcode.combat.config.implementation.PluginConfig; import com.eternalcode.combat.fight.drop.DropController; +import com.eternalcode.combat.fight.drop.DropKeepInventoryService; import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl; +import com.eternalcode.combat.fight.drop.DropService; import com.eternalcode.combat.fight.drop.DropServiceImpl; import com.eternalcode.combat.fight.drop.impl.PercentDropModifier; import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier; -import com.eternalcode.combat.fight.FightTagCommand; -import com.eternalcode.combat.fight.controller.FightActionBlockerController; -import com.eternalcode.combat.fight.controller.FightMessageController; -import com.eternalcode.combat.fight.controller.FightTagController; -import com.eternalcode.combat.fight.controller.FightUnTagController; import com.eternalcode.combat.fight.effect.FightEffectController; -import com.eternalcode.combat.event.EventManager; -import com.eternalcode.combat.fight.FightManagerImpl; -import com.eternalcode.combat.fight.FightTask; +import com.eternalcode.combat.fight.effect.FightEffectService; import com.eternalcode.combat.fight.effect.FightEffectServiceImpl; +import com.eternalcode.combat.fight.firework.FireworkController; +import com.eternalcode.combat.fight.knockback.KnockbackRegionController; +import com.eternalcode.combat.fight.knockback.KnockbackService; import com.eternalcode.combat.fight.logout.LogoutController; import com.eternalcode.combat.fight.logout.LogoutService; import com.eternalcode.combat.fight.pearl.FightPearlController; +import com.eternalcode.combat.fight.pearl.FightPearlService; import com.eternalcode.combat.fight.pearl.FightPearlServiceImpl; +import com.eternalcode.combat.fight.tagout.FightTagOutCommand; import com.eternalcode.combat.fight.tagout.FightTagOutController; +import com.eternalcode.combat.fight.tagout.FightTagOutService; import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl; -import com.eternalcode.combat.fight.tagout.FightTagOutCommand; +import com.eternalcode.combat.handler.InvalidUsageHandlerImpl; +import com.eternalcode.combat.handler.MissingPermissionHandlerImpl; import com.eternalcode.combat.notification.NoticeService; -import com.eternalcode.combat.fight.knockback.KnockbackRegionController; import com.eternalcode.combat.region.RegionProvider; import com.eternalcode.combat.updater.UpdaterNotificationController; import com.eternalcode.combat.updater.UpdaterService; @@ -61,7 +62,6 @@ import dev.rollczi.litecommands.bukkit.LiteBukkitFactory; import dev.rollczi.litecommands.bukkit.LiteBukkitMessages; import dev.rollczi.litecommands.folia.FoliaExtension; -import java.time.Duration; import net.kyori.adventure.platform.AudienceProvider; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -73,6 +73,7 @@ import org.bukkit.plugin.java.JavaPlugin; import java.io.File; +import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @@ -183,6 +184,7 @@ public void onEnable() { new FightActionBlockerController(this.fightManager, noticeService, pluginConfig, server), new FightPearlController(pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService), new DeathEffectController(pluginConfig), + new DeathCommandController(pluginConfig, this.fightManager, server), new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage), new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server), new FightEffectController(pluginConfig.effect, this.fightEffectService, this.fightManager, server), diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CommandSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CommandSettings.java index d0819515..f4534b50 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CommandSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CommandSettings.java @@ -23,4 +23,40 @@ public class CommandSettings extends OkaeriConfig { "tpa", "tpaccept" ); + + @Comment({ + "# List of commands that will be executed from console after player death.", + "# Use {player} to represent the name of the player who died and {killer} for the killer's name (if applicable)." + }) + public List consolePostDeathCommands = List.of( + "broadcast {player} has died in combat!" + ); + + @Comment("# When this is set to true, the plugin will execute the console commands only after the dead player has respawned.") + public boolean deferConsoleAfterRespawn = false; + + @Comment({ + "# List of commands that will be executed from the dead player's perspective after death.", + "# Use {player} to represent the name of the player who died and {killer} for the killer's name (if applicable)." + }) + public List deadPostDeathCommands = List.of( + "say You have died in combat!" + ); + + @Comment("# When this is set to true, the plugin will execute the commands above only after the dead player has respawned.") + public boolean deferDeadAfterRespawn = true; + + @Comment({ + "# List of commands that will be executed from the killer's perspective after killing a player.", + "# Use {player} to represent the name of the player who was killed and {killer} for the killer's name (if applicable)." + }) + public List killerPostDeathCommands = List.of( + "say You have killed {player} in combat!" + ); + + @Comment("# When this is set to true, the plugin will only execute the post-death commands if the players were tagged") + public boolean onlyExecuteIfTagged = true; + + @Comment("# The returned string when the killer is unknown") + public String unknownKillerPlaceholder = "Unknown"; } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java index bc29665f..4a328765 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java @@ -87,7 +87,8 @@ public class PluginConfig extends OkaeriConfig { @Comment({ " ", "# Settings related to commands during combat.", - "# Configure command restrictions and behaviors for players in combat." + "# Configure command restrictions and behaviors for players in combat.", + "# You can also execute which commands will be executed post-death and on logout of the player." }) public CommandSettings commands = new CommandSettings(); diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandController.java new file mode 100644 index 00000000..98676b07 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandController.java @@ -0,0 +1,169 @@ +package com.eternalcode.combat.fight.death; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.FightTag; +import com.eternalcode.combat.fight.event.CauseOfUnTag; +import com.eternalcode.combat.fight.event.FightUntagEvent; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerRespawnEvent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class DeathCommandController implements Listener { + + private final PluginConfig config; + private final FightManager fightManager; + private final Server server; + + private final Map> pendingCommands = new ConcurrentHashMap<>(); + private final Set handledByUntag = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public DeathCommandController(PluginConfig config, FightManager fightManager, Server server) { + this.config = config; + this.fightManager = fightManager; + this.server = server; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onPlayerUntag(FightUntagEvent event) { + UUID playerUUID = event.getPlayer(); + CauseOfUnTag cause = event.getCause(); + + if (cause != CauseOfUnTag.DEATH && cause != CauseOfUnTag.DEATH_BY_PLAYER) { + return; + } + + Player deadPlayer = this.server.getPlayer(playerUUID); + + if (deadPlayer == null) { + return; + } + + this.handledByUntag.add(playerUUID); + this.executeDeathCommands(deadPlayer); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onPlayerDeath(PlayerDeathEvent event) { + if (this.config.commands.onlyExecuteIfTagged) { + return; + } + + Player deadPlayer = event.getEntity(); + UUID playerUUID = deadPlayer.getUniqueId(); + + if (this.handledByUntag.remove(playerUUID)) { + return; + } + + this.executeDeathCommands(deadPlayer); + } + + @EventHandler(priority = EventPriority.MONITOR) + void onPlayerRespawn(PlayerRespawnEvent event) { + Player player = event.getPlayer(); + UUID playerUUID = player.getUniqueId(); + + this.handledByUntag.remove(playerUUID); + + List commands = this.pendingCommands.remove(playerUUID); + + if (commands == null) { + return; + } + + for (PendingCommand pending : commands) { + switch (pending.executor()) { + case CONSOLE -> this.server.dispatchCommand(this.server.getConsoleSender(), pending.command()); + case DEAD_PLAYER -> this.server.dispatchCommand(player, pending.command()); + } + } + } + + private void executeDeathCommands(Player deadPlayer) { + UUID playerUUID = deadPlayer.getUniqueId(); + String deadPlayerName = deadPlayer.getName(); + String killerName = this.resolveKillerName(playerUUID, deadPlayer); + + List deferred = new ArrayList<>(); + + for (String command : this.config.commands.consolePostDeathCommands) { + String resolved = this.replacePlaceholders(command, deadPlayerName, killerName); + if (this.config.commands.deferConsoleAfterRespawn) { + deferred.add(new PendingCommand(CommandSource.CONSOLE, resolved)); + } else { + this.server.dispatchCommand(this.server.getConsoleSender(), resolved); + } + } + + for (String command : this.config.commands.deadPostDeathCommands) { + String resolved = this.replacePlaceholders(command, deadPlayerName, killerName); + if (this.config.commands.deferDeadAfterRespawn) { + deferred.add(new PendingCommand(CommandSource.DEAD_PLAYER, resolved)); + } else { + this.server.dispatchCommand(deadPlayer, resolved); + } + } + + Player killer = this.resolveKiller(playerUUID, deadPlayer); + + if (killer != null) { + for (String command : this.config.commands.killerPostDeathCommands) { + String resolved = this.replacePlaceholders(command, deadPlayerName, killerName); + this.server.dispatchCommand(killer, resolved); + } + } + + if (!deferred.isEmpty()) { + this.pendingCommands.put(playerUUID, deferred); + } + } + + + private String resolveKillerName(UUID deadPlayerUUID, Player deadPlayer) { + Player killer = this.resolveKiller(deadPlayerUUID, deadPlayer); + return killer != null ? killer.getName() : this.config.commands.unknownKillerPlaceholder; + } + + private Player resolveKiller(UUID deadPlayerUUID, Player deadPlayer) { + Player killer = deadPlayer.getKiller(); + + if (killer != null) { + return killer; + } + + FightTag tag = this.fightManager.getTag(deadPlayerUUID); + + if (tag != null && tag.getTagger() != null) { + return this.server.getPlayer(tag.getTagger()); + } + + return null; + } + + private String replacePlaceholders(String command, String playerName, String killerName) { + return command + .replace("{player}", playerName) + .replace("{killer}", killerName); + } + + private enum CommandSource { + CONSOLE, + DEAD_PLAYER + } + + private record PendingCommand(CommandSource executor, String command) { + } +}