diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 81aa7270..57865e6d 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -30,6 +30,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 1b9e66aa..012d8fd3 100644 --- a/eternalcombat-plugin/build.gradle.kts +++ b/eternalcombat-plugin/build.gradle.kts @@ -1,6 +1,6 @@ -import net.minecrell.pluginyml.paper.PaperPluginDescription import net.minecrell.pluginyml.bukkit.BukkitPluginDescription +import net.minecrell.pluginyml.paper.PaperPluginDescription plugins { `eternalcombat-java` 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 4385582c..e44e8c77 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java @@ -19,13 +19,16 @@ import com.eternalcode.combat.fight.blocker.ElytraBlocker; import com.eternalcode.combat.fight.blocker.ElytraEquipBlocker; import com.eternalcode.combat.fight.blocker.FlyingBlocker; +import com.eternalcode.combat.fight.blocker.InventoryContainersBlocker; +import com.eternalcode.combat.fight.blocker.PlaceBlockBlocker; 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.blocker.InventoryContainersBlocker; 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.DeathCommandDispatcher; import com.eternalcode.combat.fight.death.DeathFlareController; import com.eternalcode.combat.fight.death.DeathLightningController; import com.eternalcode.combat.fight.drop.DropController; @@ -35,7 +38,6 @@ 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.blocker.PlaceBlockBlocker; import com.eternalcode.combat.fight.effect.FightEffectController; import com.eternalcode.combat.fight.effect.FightEffectService; import com.eternalcode.combat.fight.effect.FightEffectServiceImpl; @@ -70,7 +72,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.text.minimessage.MiniMessage; import org.bstats.bukkit.Metrics; import org.bukkit.Server; @@ -80,6 +81,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; @@ -182,12 +184,15 @@ public void onEnable() { new PlayersHealthDropModifier(pluginConfig.drop, logoutService) ).forEach(this.dropService::registerModifier); + DeathCommandDispatcher deathCommandDispatcher = new DeathCommandDispatcher(server); + eventManager.subscribe( new FightTagController(this.fightManager, pluginConfig), new FightUnTagController(this.fightManager, pluginConfig, logoutService), new FightBypassAdminController(server, pluginConfig), new FightBypassPermissionController(server, pluginConfig), new FightBypassCreativeController(server, pluginConfig), + new DeathCommandController(pluginConfig, this.fightManager, deathCommandDispatcher, scheduler, server), new PlaceBlockBlocker(this.fightManager, noticeService, pluginConfig), new PearlController(pluginConfig, this.pearlService, noticeService, fightManager), new TridentController(pluginConfig, noticeService, this.fightManager, this.tridentService, server), 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 6534cf0f..77cfa6dc 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 @@ -53,7 +53,8 @@ public class PluginConfig extends OkaeriConfig { @Comment({ " ", - "# This section contains effects displayed on death of the player" + "# This section contains effects displayed on death of the player", + "# You can also execute which commands will be executed post-death and on logout of the player." }) public DeathSettings death = new DeathSettings(); 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..9b84241d --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandController.java @@ -0,0 +1,138 @@ +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 com.eternalcode.commons.scheduler.Scheduler; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import java.time.Duration; +import java.util.UUID; +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.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; + +public final class DeathCommandController implements Listener { + + private final Cache killerNames = Caffeine.newBuilder() + .expireAfterWrite(Duration.ofMinutes(2)) + .build(); + + private final PluginConfig config; + private final FightManager fightManager; + private final DeathCommandDispatcher dispatcher; + private final Scheduler scheduler; + private final Server server; + + public DeathCommandController( + PluginConfig config, + FightManager fightManager, + DeathCommandDispatcher dispatcher, + Scheduler scheduler, + Server server + ) { + this.config = config; + this.fightManager = fightManager; + this.dispatcher = dispatcher; + this.scheduler = scheduler; + this.server = server; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onUntag(FightUntagEvent event) { + Player player = this.server.getPlayer(event.getPlayer()); + + if (player == null) { + return; + } + + if (!this.isDeath(event.getCause())) { + this.scheduler.run(() -> this.dispatchUntag(player)); + return; + } + + Player killer = this.resolveKiller(player); + String killerName = killer != null ? killer.getName() : this.unknownKiller(); + + this.killerNames.put(player.getUniqueId(), killerName); + this.dispatcher.dispatch(this.config.death.onDeathInCombat, player, killerName); + + if (killer != null) { + this.dispatcher.dispatch( + this.config.death.killerPostDeathCommands, + killer, + player.getName(), + killerName + ); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + void onDeath(PlayerDeathEvent event) { + Player player = event.getEntity(); + + this.dispatcher.dispatch( + this.config.death.onAnyDeath, + player, + this.killerNames.get(player.getUniqueId(), ignored -> this.resolveKillerName(player)) + ); + } + + @EventHandler(priority = EventPriority.MONITOR) + void onRespawn(PlayerRespawnEvent event) { + Player player = event.getPlayer(); + String killerName = this.killerNames.asMap().remove(player.getUniqueId()); + + this.dispatcher.dispatch( + this.config.death.afterRespawn, + player, + killerName != null ? killerName : this.unknownKiller() + ); + } + + @EventHandler + void onQuit(PlayerQuitEvent event) { + this.killerNames.invalidate(event.getPlayer().getUniqueId()); + } + + private Player resolveKiller(Player player) { + Player killer = player.getKiller(); + + if (killer != null) { + return killer; + } + + FightTag tag = this.fightManager.getTag(player.getUniqueId()); + return tag == null ? null : this.server.getPlayer(tag.getTagger()); + } + + private String resolveKillerName(Player player) { + Player killer = this.resolveKiller(player); + return killer != null ? killer.getName() : this.unknownKiller(); + } + + private void dispatchUntag(Player player) { + if (player.isOnline()) { + this.dispatcher.dispatch( + this.config.death.onUntag, + player, + this.unknownKiller() + ); + } + } + + private boolean isDeath(CauseOfUnTag cause) { + return cause == CauseOfUnTag.DEATH || cause == CauseOfUnTag.DEATH_BY_PLAYER; + } + + private String unknownKiller() { + return this.config.death.unknownKillerPlaceholder; + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandDispatcher.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandDispatcher.java new file mode 100644 index 00000000..e66b7c6f --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathCommandDispatcher.java @@ -0,0 +1,46 @@ +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 DeathCommandDispatcher { + + private final Server server; + + public DeathCommandDispatcher(Server server) { + this.server = server; + } + + public void dispatch(DeathSettings.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 trimmed = command.trim(); + + if (trimmed.isEmpty()) { + continue; + } + + if (trimmed.startsWith("/")) { + trimmed = trimmed.substring(1); + } + + String resolved = this.replacePlaceholders(trimmed, 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/DeathSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/death/DeathSettings.java index 8b4c8f1b..75e7cce6 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/respawn/untag.", + "You can use {PLAYER} to represent the name of the player who died and {KILLER} for the killer's name (if applicable).", + "Do not include the leading slash (/) in the commands.", + "", + "Commands executed when a player dies in combat" + }) + public PostDeathCommands onDeathInCombat = new PostDeathCommands(); + + @Comment("Commands executed on any player death") + public PostDeathCommands onAnyDeath = new PostDeathCommands(); + + @Comment("Commands executed after a player respawns") + public PostDeathCommands afterRespawn = new PostDeathCommands(); + + @Comment("Commands executed when a player is untagged from combat") + 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).", + "Example: 'give {KILLER} diamond 1' will give the killer a diamond after killing a player." + }) + public List killerPostDeathCommands = List.of(); + + @Comment("The returned string when the killer is unknown") + public String unknownKillerPlaceholder = "Unknown"; }