diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index d490d8cd..15584b9d 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -34,6 +34,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.0" } diff --git a/eternalcombat-plugin/build.gradle.kts b/eternalcombat-plugin/build.gradle.kts index 6edfab7f..eb859ffa 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` @@ -96,11 +96,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 48874171..6adfe4bc 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java @@ -1,55 +1,59 @@ 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.DeathCommandExecutor; +import com.eternalcode.combat.fight.death.DeathCommandService; import com.eternalcode.combat.fight.death.DeathFlareController; import com.eternalcode.combat.fight.death.DeathLightningController; -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.death.KillerResolver; 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; @@ -62,7 +66,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; @@ -74,6 +77,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; @@ -175,6 +179,10 @@ public void onEnable() { new PlayersHealthDropModifier(pluginConfig.drop, logoutService) ).forEach(this.dropService::registerModifier); + KillerResolver killerResolver = new KillerResolver(this.fightManager, server, pluginConfig); + DeathCommandExecutor deathCommandExecutor = new DeathCommandExecutor(server); + DeathCommandService deathCommandService = new DeathCommandService(pluginConfig, this.fightManager, killerResolver, deathCommandExecutor); + eventManager.subscribe( new FightTagController(this.fightManager, pluginConfig), new FightUnTagController(this.fightManager, pluginConfig, logoutService), @@ -183,6 +191,7 @@ public void onEnable() { new FightBypassCreativeController(server, pluginConfig), new FightActionBlockerController(this.fightManager, noticeService, pluginConfig, server), new FightPearlController(pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService), + new DeathCommandController(deathCommandService, server), new DeathFlareController(pluginConfig, server, scheduler, this), new DeathLightningController(pluginConfig, server), new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage), 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..c7a42d4a --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandController.java @@ -0,0 +1,42 @@ +package com.eternalcode.combat.fight.death; + +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; + +public class DeathCommandController implements Listener { + + private final DeathCommandService deathCommandService; + private final Server server; + + public DeathCommandController(DeathCommandService deathCommandService, Server server) { + this.deathCommandService = deathCommandService; + this.server = server; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onPlayerUntag(FightUntagEvent event) { + Player player = this.server.getPlayer(event.getPlayer()); + + if (player == null) { + return; + } + + this.deathCommandService.handleUntag(player, event.getCause()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onPlayerDeath(PlayerDeathEvent event) { + this.deathCommandService.handleDeath(event.getEntity()); + } + + @EventHandler(priority = EventPriority.MONITOR) + void onPlayerRespawn(PlayerRespawnEvent event) { + this.deathCommandService.handleRespawn(event.getPlayer()); + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandExecutor.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandExecutor.java new file mode 100644 index 00000000..01838e5c --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandExecutor.java @@ -0,0 +1,36 @@ +package com.eternalcode.combat.fight.death; + +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Collection; + +public class DeathCommandExecutor { + + private final Server server; + + public DeathCommandExecutor(Server server) { + this.server = server; + } + + public void dispatch(DeathSettings.PostDeathCommandSettings.PostDeathCommands settings, Player player, String killerName) { + String playerName = player.getName(); + + this.dispatch(settings.console, this.server.getConsoleSender(), playerName, killerName); + this.dispatch(settings.player, player, playerName, killerName); + } + + public void dispatch(Collection commands, CommandSender sender, String playerName, String killerName) { + for (String command : commands) { + String resolved = this.replacePlaceholders(command, playerName, killerName); + this.server.dispatchCommand(sender, resolved); + } + } + + private String replacePlaceholders(String command, String playerName, String killerName) { + return command + .replace("{player}", playerName) + .replace("{killer}", killerName); + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandService.java new file mode 100644 index 00000000..48ad5f45 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandService.java @@ -0,0 +1,84 @@ +package com.eternalcode.combat.fight.death; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.event.CauseOfUnTag; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class DeathCommandService { + + private final PluginConfig config; + private final FightManager fightManager; + private final KillerResolver killerResolver; + private final DeathCommandExecutor executor; + + private final Map killerNames = new ConcurrentHashMap<>(); + private final Set handledByUntag = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public DeathCommandService(PluginConfig config, FightManager fightManager, KillerResolver killerResolver, DeathCommandExecutor executor) { + this.config = config; + this.fightManager = fightManager; + this.killerResolver = killerResolver; + this.executor = executor; + } + + public void handleUntag(Player player, CauseOfUnTag cause) { + UUID playerUUID = player.getUniqueId(); + + if (cause == CauseOfUnTag.DEATH || cause == CauseOfUnTag.DEATH_BY_PLAYER) { + String killerName = this.killerResolver.resolveKillerName(playerUUID, player); + this.handleDeathInCombat(player, killerName); + this.handledByUntag.add(playerUUID); + this.killerNames.put(playerUUID, killerName); + return; + } + + this.executor.dispatch(this.config.death.postDeathCommands.onUntag, player, this.config.death.postDeathCommands.unknownKillerPlaceholder); + } + + public void handleDeath(Player player) { + UUID playerUUID = player.getUniqueId(); + String killerName = this.killerResolver.resolveKillerName(playerUUID, player); + + this.killerNames.putIfAbsent(playerUUID, killerName); + + this.executor.dispatch(this.config.death.postDeathCommands.onAnyDeath, player, killerName); + + if (this.handledByUntag.remove(playerUUID)) { + return; + } + + if (this.fightManager.isInCombat(playerUUID)) { + this.handleDeathInCombat(player, killerName); + } + } + + public void handleRespawn(Player player) { + UUID playerUUID = player.getUniqueId(); + + this.handledByUntag.remove(playerUUID); + + String killerName = this.killerNames.remove(playerUUID); + if (killerName == null) { + killerName = this.config.death.postDeathCommands.unknownKillerPlaceholder; + } + + this.executor.dispatch(this.config.death.postDeathCommands.afterRespawn, player, killerName); + } + + private void handleDeathInCombat(Player player, String killerName) { + this.executor.dispatch(this.config.death.postDeathCommands.onDeathInCombat, player, killerName); + + Player killer = this.killerResolver.resolveKiller(player.getUniqueId(), player); + if (killer != null) { + this.executor.dispatch(this.config.death.postDeathCommands.killerPostDeathCommands, killer, player.getName(), killerName); + } + } +} + diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathSettings.java index 8b4c8f1b..c8c48b1e 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathSettings.java @@ -5,6 +5,8 @@ import eu.okaeri.configs.annotation.Comment; import org.bukkit.FireworkEffect; +import java.util.List; + public class DeathSettings extends OkaeriConfig { @Comment({ @@ -74,4 +76,37 @@ public static class FlareSettings extends OkaeriConfig { public int secondaryParticleCount = 3; } + + @Comment({ + "Commands that will be executed after a player's death.", + "You can use {player} to represent the name of the player who died and {killer} for the killer's name (if applicable)." + }) + public PostDeathCommandSettings postDeathCommands = new PostDeathCommandSettings(); + + public static class PostDeathCommandSettings extends OkaeriConfig { + + public PostDeathCommands onDeathInCombat = new PostDeathCommands(); + + public PostDeathCommands onAnyDeath = new PostDeathCommands(); + + public PostDeathCommands afterRespawn = new PostDeathCommands(); + + public PostDeathCommands onUntag = new PostDeathCommands(); + + public static class PostDeathCommands extends OkaeriConfig { + public List console = List.of(); + public List player = List.of(); + } + + @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("# The returned string when the killer is unknown") + public String unknownKillerPlaceholder = "Unknown"; + } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/KillerResolver.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/KillerResolver.java new file mode 100644 index 00000000..814f6449 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/KillerResolver.java @@ -0,0 +1,44 @@ +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 org.bukkit.Server; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class KillerResolver { + + private final FightManager fightManager; + private final Server server; + private final PluginConfig config; + + public KillerResolver(FightManager fightManager, Server server, PluginConfig config) { + this.fightManager = fightManager; + this.server = server; + this.config = config; + } + + public 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; + } + + public String resolveKillerName(UUID deadPlayerUUID, Player deadPlayer) { + Player killer = this.resolveKiller(deadPlayerUUID, deadPlayer); + return killer != null ? killer.getName() : this.config.death.postDeathCommands.unknownKillerPlaceholder; + } +} +