Compare commits
3 Commits
4c1fd4d228
...
2b6480c880
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b6480c880 | ||
|
|
4cf4361286 | ||
|
|
016ab17ef2 |
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
11
TODO.md
11
TODO.md
|
|
@ -139,11 +139,12 @@ Track each registered player in one of these states:
|
||||||
## 🧭 Commands
|
## 🧭 Commands
|
||||||
|
|
||||||
### `/hg stuck`
|
### `/hg stuck`
|
||||||
- [ ] Teleport player to the nearest safe grass block
|
- [x] Teleport player to the nearest safe grass block
|
||||||
- [ ] Require minimum height threshold to prevent abuse (configurable)
|
- [x] Require minimum height threshold to prevent abuse (configurable)
|
||||||
- [ ] Launch fireworks at the destination on use
|
- [x] Launch fireworks at the destination on use
|
||||||
- [ ] Configure firework count/type
|
- [x] Configure firework count/type
|
||||||
- [ ] Optional cooldown to prevent spam
|
- [x] Optional cooldown to prevent spam
|
||||||
|
- [x] Warmup period (configurable) that cancels on movement or damage
|
||||||
|
|
||||||
### `/hg stats [player]`
|
### `/hg stats [player]`
|
||||||
- [ ] Show overall stats for the specified player (or self if no argument)
|
- [ ] Show overall stats for the specified player (or self if no argument)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
package com.alttd.hunger_games.commands;
|
package com.alttd.hunger_games.commands;
|
||||||
|
|
||||||
import com.alttd.hunger_games.Main;
|
import com.alttd.hunger_games.Main;
|
||||||
import com.alttd.hunger_games.commands.subcommands.Register;
|
import com.alttd.hunger_games.commands.subcommands.*;
|
||||||
import com.alttd.hunger_games.commands.subcommands.Reload;
|
|
||||||
import com.alttd.hunger_games.commands.subcommands.RoundState;
|
|
||||||
import com.alttd.hunger_games.commands.subcommands.StartRound;
|
|
||||||
import com.alttd.hunger_games.config.Messages;
|
import com.alttd.hunger_games.config.Messages;
|
||||||
import com.alttd.hunger_games.services.PlayerService;
|
import com.alttd.hunger_games.services.PlayerService;
|
||||||
import com.alttd.hunger_games.services.Round;
|
import com.alttd.hunger_games.services.Round;
|
||||||
|
|
@ -40,17 +37,22 @@ public class BaseCommand implements CommandExecutor, TabExecutor {
|
||||||
new Reload(main),
|
new Reload(main),
|
||||||
new RoundState(roundService),
|
new RoundState(roundService),
|
||||||
new Register(playerService),
|
new Register(playerService),
|
||||||
new StartRound(round, roundService)
|
new StartRound(round, roundService),
|
||||||
|
new Stuck(main)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String cmd, @NotNull String[] args) {
|
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String cmd, @NotNull String[] args) {
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
commandSender.sendRichMessage(Messages.HELP.HELP_MESSAGE_WRAPPER.replaceAll("<commands>", subCommands.stream()
|
commandSender.sendRichMessage(Messages.HELP.HELP_MESSAGE_WRAPPER.replaceAll("<commands>",
|
||||||
.filter(subCommand -> commandSender.hasPermission(subCommand.getPermission()))
|
subCommands.stream()
|
||||||
|
.filter(subCommand -> commandSender.hasPermission(
|
||||||
|
subCommand.getPermission()))
|
||||||
.map(SubCommand::getHelpMessage)
|
.map(SubCommand::getHelpMessage)
|
||||||
.collect(Collectors.joining("\n"))));
|
.collect(Collectors.joining(
|
||||||
|
"\n"))
|
||||||
|
));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +62,9 @@ public class BaseCommand implements CommandExecutor, TabExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!commandSender.hasPermission(subCommand.getPermission())) {
|
if (!commandSender.hasPermission(subCommand.getPermission())) {
|
||||||
commandSender.sendRichMessage(Messages.GENERIC.NO_PERMISSION, Placeholder.parsed("permission", subCommand.getPermission()));
|
commandSender.sendRichMessage(Messages.GENERIC.NO_PERMISSION,
|
||||||
|
Placeholder.parsed("permission", subCommand.getPermission())
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
package com.alttd.hunger_games.commands.subcommands;
|
||||||
|
import com.alttd.hunger_games.Main;
|
||||||
|
import com.alttd.hunger_games.commands.SubCommand;
|
||||||
|
import com.alttd.hunger_games.config.Config;
|
||||||
|
import com.alttd.hunger_games.config.Messages;
|
||||||
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
|
import org.bukkit.Color;
|
||||||
|
import org.bukkit.FireworkEffect;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Firework;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
|
import org.bukkit.inventory.meta.FireworkMeta;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class Stuck extends SubCommand implements Listener {
|
||||||
|
|
||||||
|
private final Main main;
|
||||||
|
private final Map<UUID, Instant> cooldowns = new HashMap<>();
|
||||||
|
private final Map<UUID, Warmup> warmups = new HashMap<>();
|
||||||
|
|
||||||
|
public Stuck(Main main) {
|
||||||
|
this.main = main;
|
||||||
|
main.getServer().getPluginManager().registerEvents(this, main);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Warmup(Location location, BukkitTask task) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender commandSender, String[] args) {
|
||||||
|
if (!(commandSender instanceof Player player)) {
|
||||||
|
commandSender.sendRichMessage(Messages.GENERIC.PLAYER_ONLY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warmups.containsKey(player.getUniqueId())) {
|
||||||
|
player.sendRichMessage(Messages.STUCK.ALREADY_WARMING_UP);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.getLocation().getY() < Config.DESTINATION.STUCK_MIN_HEIGHT) {
|
||||||
|
player.sendRichMessage(Messages.STUCK.TOO_LOW,
|
||||||
|
Placeholder.parsed("height", String.valueOf(Config.DESTINATION.STUCK_MIN_HEIGHT))
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOnCooldown(player)) {
|
||||||
|
Duration remaining = getRemainingCooldown(player);
|
||||||
|
player.sendRichMessage(Messages.STUCK.ON_COOLDOWN,
|
||||||
|
Placeholder.parsed("time", remaining.toSeconds() + "s")
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<Location> safeLocation = findSafeLocation(player.getLocation());
|
||||||
|
if (safeLocation.isEmpty()) {
|
||||||
|
player.sendRichMessage(Messages.STUCK.NO_SAFE_LOCATION);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
startWarmup(player, safeLocation.get());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startWarmup(Player player, Location safeLocation) {
|
||||||
|
Duration warmupTime = Config.DESTINATION.STUCK_WARMUP;
|
||||||
|
if (warmupTime.isZero() || warmupTime.isNegative()) {
|
||||||
|
teleport(player, safeLocation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.sendRichMessage(Messages.STUCK.WARMUP_STARTED,
|
||||||
|
Placeholder.parsed("time", warmupTime.toSeconds() + "s")
|
||||||
|
);
|
||||||
|
|
||||||
|
BukkitTask task = main.getServer().getScheduler().runTaskLater(main, () -> {
|
||||||
|
warmups.remove(player.getUniqueId());
|
||||||
|
teleport(player, safeLocation);
|
||||||
|
}, warmupTime.toSeconds() * 20L);
|
||||||
|
|
||||||
|
warmups.put(player.getUniqueId(), new Warmup(player.getLocation(), task));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void teleport(Player player, Location safeLocation) {
|
||||||
|
player.teleport(safeLocation.add(0.5, 1, 0.5));
|
||||||
|
spawnFireworks(player.getLocation());
|
||||||
|
player.sendRichMessage(Messages.STUCK.TELEPORTED);
|
||||||
|
cooldowns.put(player.getUniqueId(), Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
|
UUID uuid = event.getPlayer().getUniqueId();
|
||||||
|
Warmup warmup = warmups.get(uuid);
|
||||||
|
if (warmup == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location from = warmup.location();
|
||||||
|
Location to = event.getTo();
|
||||||
|
|
||||||
|
if (from.getWorld() != to.getWorld() || from.distanceSquared(to) > 0.25) {
|
||||||
|
warmup.task().cancel();
|
||||||
|
warmups.remove(uuid);
|
||||||
|
event.getPlayer().sendRichMessage(Messages.STUCK.WARMUP_CANCELLED_MOVEMENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerDamage(EntityDamageEvent event) {
|
||||||
|
if (!(event.getEntity() instanceof Player player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Warmup warmup = warmups.get(player.getUniqueId());
|
||||||
|
if (warmup == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
warmup.task().cancel();
|
||||||
|
warmups.remove(player.getUniqueId());
|
||||||
|
player.sendRichMessage(Messages.STUCK.WARMUP_CANCELLED_DAMAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOnCooldown(Player player) {
|
||||||
|
Instant lastUse = cooldowns.get(player.getUniqueId());
|
||||||
|
if (lastUse == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Duration.between(lastUse, Instant.now()).compareTo(Config.DESTINATION.STUCK_COOLDOWN) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Duration getRemainingCooldown(Player player) {
|
||||||
|
Instant lastUse = cooldowns.get(player.getUniqueId());
|
||||||
|
if (lastUse == null) {
|
||||||
|
return Duration.ZERO;
|
||||||
|
}
|
||||||
|
Duration elapsed = Duration.between(lastUse, Instant.now());
|
||||||
|
Duration remaining = Config.DESTINATION.STUCK_COOLDOWN.minus(elapsed);
|
||||||
|
return remaining.isNegative() ? Duration.ZERO : remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Location> findSafeLocation(Location start) {
|
||||||
|
int radius = 10;
|
||||||
|
for (int r = 0; r <= radius; r++) {
|
||||||
|
for (int x = -r; x <= r; x++) {
|
||||||
|
for (int z = -r; z <= r; z++) {
|
||||||
|
if (Math.abs(x) != r && Math.abs(z) != r) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location baseLocation = start.clone().add(x, 0, z);
|
||||||
|
Optional<Location> safeY = findSafeY(baseLocation);
|
||||||
|
if (safeY.isPresent()) {
|
||||||
|
return safeY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Location> findSafeY(Location base) {
|
||||||
|
// Search up and down a bit from current Y
|
||||||
|
for (int yOffset = -5; yOffset <= 5; yOffset++) {
|
||||||
|
Block target = base.getBlock().getRelative(0, yOffset, 0);
|
||||||
|
if (isSafe(target)) {
|
||||||
|
return Optional.of(target.getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSafe(Block block) {
|
||||||
|
return block.getType() == Material.GRASS_BLOCK &&
|
||||||
|
block.getRelative(BlockFace.UP).getType().isAir() &&
|
||||||
|
block.getRelative(BlockFace.UP).getRelative(BlockFace.UP).getType().isAir();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnFireworks(Location location) {
|
||||||
|
for (int i = 0; i < Config.DESTINATION.STUCK_FIREWORK_COUNT; i++) {
|
||||||
|
Firework firework = (Firework) location.getWorld().spawnEntity(location, EntityType.FIREWORK_ROCKET);
|
||||||
|
FireworkMeta meta = firework.getFireworkMeta();
|
||||||
|
meta.addEffect(FireworkEffect.builder()
|
||||||
|
.withColor(Color.RED, Color.ORANGE, Color.YELLOW)
|
||||||
|
.withFade(Color.ORANGE)
|
||||||
|
.with(FireworkEffect.Type.BALL_LARGE)
|
||||||
|
.trail(true)
|
||||||
|
.flicker(true)
|
||||||
|
.build());
|
||||||
|
meta.setPower(1);
|
||||||
|
firework.setFireworkMeta(meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "stuck";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpMessage() {
|
||||||
|
return Messages.HELP.STUCK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -110,6 +110,11 @@ public class Config extends AbstractConfig {
|
||||||
public static int FINALE_RADIUS = 10;
|
public static int FINALE_RADIUS = 10;
|
||||||
public static Location SPECTATOR_AREA = null;
|
public static Location SPECTATOR_AREA = null;
|
||||||
|
|
||||||
|
public static int STUCK_MIN_HEIGHT = 64;
|
||||||
|
public static int STUCK_FIREWORK_COUNT = 3;
|
||||||
|
public static Duration STUCK_COOLDOWN = Duration.ofMinutes(2);
|
||||||
|
public static Duration STUCK_WARMUP = Duration.ofSeconds(5);
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static void load() {
|
private static void load() {
|
||||||
Config.DESTINATION.START_RADIUS = config.getInt(prefix, "start-radius", START_RADIUS);
|
Config.DESTINATION.START_RADIUS = config.getInt(prefix, "start-radius", START_RADIUS);
|
||||||
|
|
@ -122,6 +127,11 @@ public class Config extends AbstractConfig {
|
||||||
DESTINATION.FINALE_RADIUS = config.getInt(prefix, "finale-radius", FINALE_RADIUS);
|
DESTINATION.FINALE_RADIUS = config.getInt(prefix, "finale-radius", FINALE_RADIUS);
|
||||||
config.getLocation(prefix, "finale-center")
|
config.getLocation(prefix, "finale-center")
|
||||||
.ifPresent(location -> DESTINATION.FINALE_CENTER = location);
|
.ifPresent(location -> DESTINATION.FINALE_CENTER = location);
|
||||||
|
|
||||||
|
STUCK_MIN_HEIGHT = config.getInt(prefix, "stuck-min-height", STUCK_MIN_HEIGHT);
|
||||||
|
STUCK_FIREWORK_COUNT = config.getInt(prefix, "stuck-firework-count", STUCK_FIREWORK_COUNT);
|
||||||
|
STUCK_COOLDOWN = Duration.ofSeconds(config.getInt(prefix, "stuck-cooldown-seconds", (int) STUCK_COOLDOWN.toSeconds()));
|
||||||
|
STUCK_WARMUP = Duration.ofSeconds(config.getInt(prefix, "stuck-warmup-seconds", (int) STUCK_WARMUP.toSeconds()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ public class Messages extends AbstractConfig {
|
||||||
public static String REGISTER = "<green>Register a player for the game: <gold>/hg register <player></gold></green>";
|
public static String REGISTER = "<green>Register a player for the game: <gold>/hg register <player></gold></green>";
|
||||||
public static String START_ROUND = "<green>Start the game: <gold>/hg start</gold></green>";
|
public static String START_ROUND = "<green>Start the game: <gold>/hg start</gold></green>";
|
||||||
public static String RELOAD = "<green>Reload config and messages: <gold>/hg reload</gold></green>";
|
public static String RELOAD = "<green>Reload config and messages: <gold>/hg reload</gold></green>";
|
||||||
|
public static String STUCK = "<green>Teleport to safety if stuck: <gold>/hg stuck</gold></green>";
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static void load() {
|
private static void load() {
|
||||||
|
|
@ -39,6 +40,7 @@ public class Messages extends AbstractConfig {
|
||||||
REGISTER = config.getString(prefix, "register", REGISTER);
|
REGISTER = config.getString(prefix, "register", REGISTER);
|
||||||
START_ROUND = config.getString(prefix, "start", START_ROUND);
|
START_ROUND = config.getString(prefix, "start", START_ROUND);
|
||||||
RELOAD = config.getString(prefix, "reload", RELOAD);
|
RELOAD = config.getString(prefix, "reload", RELOAD);
|
||||||
|
STUCK = config.getString(prefix, "stuck", STUCK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,13 +121,28 @@ public class Messages extends AbstractConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class START_ROUND {
|
public static class STUCK {
|
||||||
private static final String prefix = "start-round.";
|
private static final String prefix = "stuck.";
|
||||||
public static String CAN_NOT_START_ROUND = "<red>The round can not be started because the current state is <state>.</red>";
|
|
||||||
|
public static String TELEPORTED = "<green>You have been teleported to safety!</green>";
|
||||||
|
public static String TOO_LOW = "<red>You are too low to use this command (minimum height: <height>). If you are truly stuck here dm a staff member for help</red>";
|
||||||
|
public static String NO_SAFE_LOCATION = "<red>Unable to find a safe grass block nearby</red>";
|
||||||
|
public static String ON_COOLDOWN = "<red>You must wait <time> before using this command again</red>";
|
||||||
|
public static String WARMUP_STARTED = "<green>Teleporting in <time>. Do not move or take damage!</green>";
|
||||||
|
public static String WARMUP_CANCELLED_MOVEMENT = "<red>Teleportation cancelled due to movement.</red>";
|
||||||
|
public static String WARMUP_CANCELLED_DAMAGE = "<red>Teleportation cancelled due to taking damage.</red>";
|
||||||
|
public static String ALREADY_WARMING_UP = "<red>You are already warming up!</red>";
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static void load() {
|
private static void load() {
|
||||||
CAN_NOT_START_ROUND = config.getString(prefix, "can-not-start", CAN_NOT_START_ROUND);
|
TELEPORTED = config.getString(prefix, "teleported", TELEPORTED);
|
||||||
|
TOO_LOW = config.getString(prefix, "too-low", TOO_LOW);
|
||||||
|
NO_SAFE_LOCATION = config.getString(prefix, "no-safe-location", NO_SAFE_LOCATION);
|
||||||
|
ON_COOLDOWN = config.getString(prefix, "on-cooldown", ON_COOLDOWN);
|
||||||
|
WARMUP_STARTED = config.getString(prefix, "warmup-started", WARMUP_STARTED);
|
||||||
|
WARMUP_CANCELLED_MOVEMENT = config.getString(prefix, "warmup-cancelled-movement", WARMUP_CANCELLED_MOVEMENT);
|
||||||
|
WARMUP_CANCELLED_DAMAGE = config.getString(prefix, "warmup-cancelled-damage", WARMUP_CANCELLED_DAMAGE);
|
||||||
|
ALREADY_WARMING_UP = config.getString(prefix, "already-warming-up", ALREADY_WARMING_UP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user