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.
This commit is contained in:
Nick Guy 2025-08-11 19:02:50 +01:00
parent 3edf908b89
commit 7936a525b8
5 changed files with 172 additions and 5 deletions

View file

@ -33,4 +33,7 @@ public class Reference {
public static IDataContainerRef<Boolean, Item> ITEM_LAVA_IMMUNE = new AnonymousDataContainerRef<>(key("item.lava.immune"), PersistentDataType.BOOLEAN);
public static IDataContainerRef<Boolean, Player> HARDCORE_ENABLED = new AnonymousDataContainerRef<>(key("player.hardcore.enabled"), PersistentDataType.BOOLEAN);
public static IDataContainerRef<Boolean, Player> HARDCORE_ELIMINATED = new AnonymousDataContainerRef<>(key("player.hardcore.eliminated"), PersistentDataType.BOOLEAN);
}

View file

@ -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--) {

View file

@ -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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> ctx) {
Entity executor = ctx.getSource().getExecutor();
if (!(executor instanceof Player player)) return -1;
toggleHardcoreForPlayer(player, false);
return 0;
}
private int executeRootConfirm(CommandContext<CommandSourceStack> ctx) {
return executeRoot(ctx, true);
}
@ -589,6 +657,13 @@ public class SkyblockGenCommand extends AbstractSkyblockCommand {
prg.getAwardedCriteria().forEach(prg::revokeCriteria);
}
// Clear hardcore status
IDataContainerRef<Boolean, Player> 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;
}

View file

@ -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.");
}
}
}

View file

@ -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());
}