From 7936a525b8a326e87ccabc02bd71b155e2b7f3de Mon Sep 17 00:00:00 2001 From: Nick Guy Date: Mon, 11 Aug 2025 19:02:50 +0100 Subject: [PATCH] Add Hardcore mode with player elimination mechanics. - Introduced Hardcore mode toggle via `/skyblock hardcore` command. - Implemented player elimination and spectator mode handling upon death. - Added mechanics to clear Hardcore status during player reset. - Enhanced respawn logic to respect island or Hardcore mode settings. - Registered `HardcoreHandler` for relevant events. --- .../com/ncguy/usefulskyblock/Reference.java | 3 + .../ncguy/usefulskyblock/UsefulSkyblock.java | 5 ++ .../command/SkyblockGenCommand.java | 83 ++++++++++++++++++- .../handlers/HardcoreHandler.java | 54 ++++++++++++ .../handlers/InitialisationHandler.java | 32 ++++++- 5 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/ncguy/usefulskyblock/handlers/HardcoreHandler.java diff --git a/src/main/java/com/ncguy/usefulskyblock/Reference.java b/src/main/java/com/ncguy/usefulskyblock/Reference.java index 8de3dd3..42ae78a 100644 --- a/src/main/java/com/ncguy/usefulskyblock/Reference.java +++ b/src/main/java/com/ncguy/usefulskyblock/Reference.java @@ -33,4 +33,7 @@ public class Reference { public static IDataContainerRef ITEM_LAVA_IMMUNE = new AnonymousDataContainerRef<>(key("item.lava.immune"), PersistentDataType.BOOLEAN); + public static IDataContainerRef HARDCORE_ENABLED = new AnonymousDataContainerRef<>(key("player.hardcore.enabled"), PersistentDataType.BOOLEAN); + public static IDataContainerRef HARDCORE_ELIMINATED = new AnonymousDataContainerRef<>(key("player.hardcore.eliminated"), PersistentDataType.BOOLEAN); + } diff --git a/src/main/java/com/ncguy/usefulskyblock/UsefulSkyblock.java b/src/main/java/com/ncguy/usefulskyblock/UsefulSkyblock.java index 1be852a..ec9ef17 100644 --- a/src/main/java/com/ncguy/usefulskyblock/UsefulSkyblock.java +++ b/src/main/java/com/ncguy/usefulskyblock/UsefulSkyblock.java @@ -42,6 +42,10 @@ public final class UsefulSkyblock extends JavaPlugin implements Listener { dialogMaps = new java.util.concurrent.ConcurrentHashMap<>(); } + public static UsefulSkyblock instance() { + return getPlugin(UsefulSkyblock.class); + } + @Override public void onEnable() { saveDefaultConfig(); @@ -64,6 +68,7 @@ public final class UsefulSkyblock extends JavaPlugin implements Listener { pluginManager.registerEvents(new InitialisationHandler(), this); pluginManager.registerEvents(new TeamProgressHandler(), this); pluginManager.registerEvents(new FishingHandler(), this); + pluginManager.registerEvents(new HardcoreHandler(), this); Server server = Bukkit.getServer(); for (int i = recipeProviders.length-1; i >= 0; i--) { diff --git a/src/main/java/com/ncguy/usefulskyblock/command/SkyblockGenCommand.java b/src/main/java/com/ncguy/usefulskyblock/command/SkyblockGenCommand.java index 74e6e9f..ba48a2f 100644 --- a/src/main/java/com/ncguy/usefulskyblock/command/SkyblockGenCommand.java +++ b/src/main/java/com/ncguy/usefulskyblock/command/SkyblockGenCommand.java @@ -3,6 +3,7 @@ package com.ncguy.usefulskyblock.command; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; @@ -10,11 +11,14 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; import com.ncguy.usefulskyblock.*; import com.ncguy.usefulskyblock.data.BiomedStructure; +import com.ncguy.usefulskyblock.pdc.IDataContainerRef; import com.ncguy.usefulskyblock.utils.Components; import com.ncguy.usefulskyblock.utils.TextUtils; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; import io.papermc.paper.registry.data.dialog.body.DialogBody; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.Style; @@ -39,6 +43,7 @@ import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.function.Supplier; import static com.ncguy.usefulskyblock.Reference.key; @@ -132,7 +137,8 @@ public class SkyblockGenCommand extends AbstractSkyblockCommand { teamSpawn.set(tpLoc.toVector().toBlockVector()); worldTeamCount.set(count + 1); - executor.teleport(tpLoc); + player.teleport(tpLoc); + player.setGameMode(GameMode.SURVIVAL); islandHomeLoc.set(tpLoc.toVector().toBlockVector()); Advancement a = Advancements.skyblock.SKYBLOCK_BEGIN; @@ -233,7 +239,7 @@ public class SkyblockGenCommand extends AbstractSkyblockCommand { return; } player.getInventory().clear(); - if(player.getGameMode() != GameMode.SURVIVAL) + if (player.getGameMode() != GameMode.SURVIVAL) player.setGameMode(GameMode.SURVIVAL); }; @@ -244,7 +250,9 @@ public class SkyblockGenCommand extends AbstractSkyblockCommand { b.body(List.of( DialogBody.plainMessage(Component.text("Know that this action is irreversible")), DialogBody.plainMessage(Component.newline()), - DialogBody.plainMessage(Component.text("To skip this notice in future, use the command variant ").append(Components.Command("/skyblock confirm")).append(Component.text("."))) + DialogBody.plainMessage(Component.text("To skip this notice in future, use the command variant ") + .append(Components.Command("/skyblock confirm")) + .append(Component.text("."))) )); }).thenAccept(res -> { if (res) { @@ -255,8 +263,8 @@ public class SkyblockGenCommand extends AbstractSkyblockCommand { }); return; } - doTeleport.run(); + }; if (islandHomeLoc.has()) { tryTeleport.run(); @@ -335,10 +343,70 @@ public class SkyblockGenCommand extends AbstractSkyblockCommand { root.then(team); root.executes(cmd::executeRootNoConfirm); root.then(Commands.literal("confirm").executes(cmd::executeRootConfirm)); + LiteralArgumentBuilder hardcore = Commands.literal("hardcore"); + + hardcore.executes(cmd::executeToggleHardcore); + hardcore.then(Commands.argument("player", ArgumentTypes.player()).requires(cmd::auth).executes(cmd::executeToggleHardcoreForPlayer)); + + root.then(hardcore); return root.build(); } + private void toggleHardcoreForPlayer(Player player, boolean skipConfirmationForEnable) { + if (Reference.HARDCORE_ENABLED.assign(player).getOrDefault(false)) + disableHardcore(player); + else + enableHardcore(player, skipConfirmationForEnable); + } + + private void disableHardcore(Player player) { + player.sendMessage(Component.text("Hardcore mode disabled")); + Reference.HARDCORE_ENABLED.assign(player).set(false); + Reference.HARDCORE_ELIMINATED.assign(player).set(false); + } + + private void enableHardcore(Player player, boolean skipConfirmation) { + Runnable _enableHardcore = () -> { + player.sendMessage(Component.text("Hardcore mode enabled")); + Reference.HARDCORE_ENABLED.assign(player).set(true); + Reference.HARDCORE_ELIMINATED.assign(player).set(false); + }; + + if(skipConfirmation) { + _enableHardcore.run(); + return; + } + + UsefulSkyblock.instance().confirm(player, Component.text("Enabling hardcore mode"), b -> {}) + .thenAccept(res -> { + if (res) { + _enableHardcore.run(); + } else { + player.sendMessage(Component.text("Hardcore mode not enabled")); + } + }); + } + + private int executeToggleHardcoreForPlayer(CommandContext ctx) { + PlayerSelectorArgumentResolver selector = ctx.getArgument("player", PlayerSelectorArgumentResolver.class); + Player player = null; + try { + player = selector.resolve(ctx.getSource()).getFirst(); + toggleHardcoreForPlayer(player, true); + } catch (CommandSyntaxException e) { + throw new RuntimeException(e); + } + return 0; + } + + private int executeToggleHardcore(CommandContext ctx) { + Entity executor = ctx.getSource().getExecutor(); + if (!(executor instanceof Player player)) return -1; + toggleHardcoreForPlayer(player, false); + return 0; + } + private int executeRootConfirm(CommandContext ctx) { return executeRoot(ctx, true); } @@ -589,6 +657,13 @@ public class SkyblockGenCommand extends AbstractSkyblockCommand { prg.getAwardedCriteria().forEach(prg::revokeCriteria); } + // Clear hardcore status + IDataContainerRef assign = Reference.HARDCORE_ENABLED.assign(player); + if(assign.getOrDefault(false)) + player.sendMessage("Hardcore status cleared."); + assign.remove(); + Reference.HARDCORE_ELIMINATED.assign(player).remove(); + return 0; } diff --git a/src/main/java/com/ncguy/usefulskyblock/handlers/HardcoreHandler.java b/src/main/java/com/ncguy/usefulskyblock/handlers/HardcoreHandler.java new file mode 100644 index 0000000..ddf3421 --- /dev/null +++ b/src/main/java/com/ncguy/usefulskyblock/handlers/HardcoreHandler.java @@ -0,0 +1,54 @@ +package com.ncguy.usefulskyblock.handlers; + +import com.ncguy.usefulskyblock.Reference; +import com.ncguy.usefulskyblock.UsefulSkyblock; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.bukkit.event.player.PlayerRespawnEvent; + +public class HardcoreHandler implements Listener { + + private boolean isPlayerHardcore(Player player) { + return Reference.HARDCORE_ENABLED.assign(player).getOrDefault(false); + } + + private boolean isPlayerEliminated(Player player) { + return Reference.HARDCORE_ELIMINATED.assign(player).getOrDefault(false); + } + + private void eliminate(Player player) { + Reference.HARDCORE_ELIMINATED.assign(player).set(true); + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + Player player = event.getPlayer(); + if(!isPlayerHardcore(player)) return; + eliminate(player); + } + + @EventHandler + public void onRespawn(PlayerRespawnEvent e) { + Player p = e.getPlayer(); + if (isPlayerHardcore(p) && isPlayerEliminated(p) && p.getGameMode() != GameMode.SPECTATOR) { + // Delay gamemode change by 1 tick to avoid race conflicts + Bukkit.getScheduler().runTask(UsefulSkyblock.instance(), () -> p.setGameMode(GameMode.SPECTATOR)); + } + } + + @EventHandler + public void onGamemodeChange(PlayerGameModeChangeEvent e) { + Player p = e.getPlayer(); + if (isPlayerHardcore(p) && isPlayerEliminated(p) && e.getNewGameMode() != GameMode.SPECTATOR) { + e.setCancelled(true); + p.sendMessage("You are eliminated in Hardcore and cannot change gamemode."); + } + } + + +} diff --git a/src/main/java/com/ncguy/usefulskyblock/handlers/InitialisationHandler.java b/src/main/java/com/ncguy/usefulskyblock/handlers/InitialisationHandler.java index 12e4b9c..20a2b1c 100644 --- a/src/main/java/com/ncguy/usefulskyblock/handlers/InitialisationHandler.java +++ b/src/main/java/com/ncguy/usefulskyblock/handlers/InitialisationHandler.java @@ -14,6 +14,7 @@ import org.bukkit.block.structure.StructureRotation; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.server.ServerLoadEvent; @@ -23,6 +24,7 @@ import org.bukkit.persistence.PersistentDataType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Objects; import java.util.Random; import static com.ncguy.usefulskyblock.Reference.key; @@ -45,19 +47,47 @@ public class InitialisationHandler implements Listener { player.setRespawnLocation(world.getSpawnLocation()); } + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + Player player = event.getPlayer(); + Location resLoc = player.getRespawnLocation(); + if(resLoc != null) + return; + + var islandHome = Reference.ISLAND_HOME_LOC.assign(player); + if(!islandHome.has()) + return; + + World overworld = Bukkit.getWorld(NamespacedKey.minecraft("overworld")); + player.setRespawnLocation(islandHome.get().toLocation(Objects.requireNonNull(overworld))); + + } + @EventHandler public void onPlayerPostRespawn(PlayerPostRespawnEvent event) { Player player = event.getPlayer(); player.sendMessage(Component.text("Hello, " + player.getName() + "!")); + World overworld = Objects.requireNonNull(Bukkit.getWorld(NamespacedKey.minecraft("overworld"))); + var islandHome = Reference.ISLAND_HOME_LOC.assign(player); if(islandHome.has()) { // TODO Handle respawning and such, especially when bed is missing + Location respawnLocation = player.getRespawnLocation(); + if(respawnLocation == null) { + player.teleport(islandHome.get().toLocation(overworld), PlayerTeleportEvent.TeleportCause.PLUGIN); + return; + } + + System.out.println("Respawn location: " + respawnLocation); + System.out.println("Island home: " + islandHome.get()); + System.out.println("World respawn location: " + player.getWorld().getSpawnLocation()); + return; } // If the player doesn't have a home island, respawn them in the server lobby - World world = Bukkit.getWorld(key("void")); + World world = Objects.requireNonNull(Bukkit.getWorld(key("void"))); player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); player.setRespawnLocation(world.getSpawnLocation()); }