diff --git a/commons/src/main/java/net/swofty/commons/CustomWorlds.java b/commons/src/main/java/net/swofty/commons/CustomWorlds.java index 4b65de832..797ac4159 100644 --- a/commons/src/main/java/net/swofty/commons/CustomWorlds.java +++ b/commons/src/main/java/net/swofty/commons/CustomWorlds.java @@ -18,6 +18,7 @@ public enum CustomWorlds { BEDWARS_LOBBY("hypixel_bedwars_lobby"), MURDER_MYSTERY_LOBBY("hypixel_murder_mystery_lobby"), SKYWARS_LOBBY("hypixel_skywars_lobby"), + ARCADE_LOBBY("hypixel_arcade_lobby") ; private final String folderName; diff --git a/commons/src/main/java/net/swofty/commons/ServerType.java b/commons/src/main/java/net/swofty/commons/ServerType.java index 7c7f41037..2ece9f8ca 100644 --- a/commons/src/main/java/net/swofty/commons/ServerType.java +++ b/commons/src/main/java/net/swofty/commons/ServerType.java @@ -27,7 +27,10 @@ public enum ServerType { MURDER_MYSTERY_CONFIGURATOR(false), SKYWARS_LOBBY(false), SKYWARS_GAME(false), - SKYWARS_CONFIGURATOR(false) + SKYWARS_CONFIGURATOR(false), + ARCADE_LOBBY(false), + ZOMBIES_GAME(false), + ZOMBIES_CONFIGURATOR(false) ; private final boolean isSkyBlock; diff --git a/commons/src/main/java/net/swofty/commons/zombies/ZombiesGameType.java b/commons/src/main/java/net/swofty/commons/zombies/ZombiesGameType.java new file mode 100644 index 000000000..bdef4cc01 --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/ZombiesGameType.java @@ -0,0 +1,76 @@ +package net.swofty.commons.zombies; + +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +@Getter +public enum ZombiesGameType { + NORMAL(0, "Normal", ZombiesDifficulty.NORMAL), + RIP(1, "RIP", ZombiesDifficulty.HARD); + + private final int id; + private final String displayName; + private final ZombiesDifficulty difficulty; + + ZombiesGameType(int id, String displayName, ZombiesDifficulty difficulty) { + this.id = id; + this.displayName = displayName; + this.difficulty = difficulty; + } + + public int getMaxPlayers() { + return 4; + } + + public int getMinPlayers() { + return 4; + } + + @Nullable + public static ZombiesGameType from(String field) { + for (ZombiesGameType type : values()) { + if (type.name().equalsIgnoreCase(field)) { + return type; + } + } + return null; + } + + @Nullable + public static ZombiesGameType fromDisplayName(String displayName) { + for (ZombiesGameType type : values()) { + if (type.displayName.equalsIgnoreCase(displayName)) { + return type; + } + } + return null; + } + + @Nullable + public static ZombiesGameType fromId(int id) { + for (ZombiesGameType type : values()) { + if (type.id == id) { + return type; + } + } + return null; + } + + public boolean isRIP() { + return this == RIP; + } + + @Getter + public enum ZombiesDifficulty { + NORMAL("Normal", 1.0), + HARD("Hard", 1.5); + + private final String displayName; + private final double difficultyMultiplier; + + ZombiesDifficulty(String displayName, double difficultyMultiplier) { + this.displayName = displayName; + this.difficultyMultiplier = difficultyMultiplier; + } + } +} diff --git a/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardMode.java b/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardMode.java new file mode 100644 index 000000000..70e52828b --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardMode.java @@ -0,0 +1,70 @@ +package net.swofty.commons.zombies; + +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +@Getter +public enum ZombiesLeaderboardMode { + ALL("all", "All Maps"), + DEAD_END("dead_end", "Dead End"), + BAD_BLOOD("bad_blood", "Bad Blood"), + ALIEN_ARCADIUM("alien_arcadium", "Alien Arcadium"), + PRISON("prison", "Prison"); + + private final String key; + private final String displayName; + + ZombiesLeaderboardMode(String key, String displayName) { + this.key = key; + this.displayName = displayName; + } + + private static final List ACTIVE_MAPS = Arrays.asList( + ZombiesMap.DEAD_END, + ZombiesMap.BAD_BLOOD, + ZombiesMap.ALIEN_ARCADIUM, + ZombiesMap.PRISON + ); + + public boolean includes(ZombiesMap map) { + return switch (this) { + case ALL -> ACTIVE_MAPS.contains(map); + case DEAD_END -> map == ZombiesMap.DEAD_END; + case BAD_BLOOD -> map == ZombiesMap.BAD_BLOOD; + case ALIEN_ARCADIUM -> map == ZombiesMap.ALIEN_ARCADIUM; + case PRISON -> map == ZombiesMap.PRISON; + }; + } + + public ZombiesLeaderboardMode next() { + ZombiesLeaderboardMode[] values = values(); + int nextOrdinal = (this.ordinal() + 1) % values.length; + return values[nextOrdinal]; + } + + public ZombiesLeaderboardMode previous() { + ZombiesLeaderboardMode[] values = values(); + int prevOrdinal = (this.ordinal() - 1 + values.length) % values.length; + return values[prevOrdinal]; + } + + public static ZombiesLeaderboardMode fromKey(String key) { + for (ZombiesLeaderboardMode mode : values()) { + if (mode.key.equalsIgnoreCase(key)) { + return mode; + } + } + return ALL; + } + + public static ZombiesLeaderboardMode fromMap(ZombiesMap map) { + return switch (map) { + case DEAD_END -> DEAD_END; + case BAD_BLOOD -> BAD_BLOOD; + case ALIEN_ARCADIUM -> ALIEN_ARCADIUM; + case PRISON -> PRISON; + }; + } +} diff --git a/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardPeriod.java b/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardPeriod.java new file mode 100644 index 000000000..5a89ab6c3 --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardPeriod.java @@ -0,0 +1,42 @@ +package net.swofty.commons.zombies; + +import lombok.Getter; + +@Getter +public enum ZombiesLeaderboardPeriod { + DAILY("daily", "Daily", 1), + WEEKLY("weekly", "Weekly", 7), + MONTHLY("monthly", "Monthly", 30), + LIFETIME("lifetime", "Lifetime", -1); + + private final String key; + private final String displayName; + private final int days; + + ZombiesLeaderboardPeriod(String key, String displayName, int days) { + this.key = key; + this.displayName = displayName; + this.days = days; + } + + public ZombiesLeaderboardPeriod next() { + ZombiesLeaderboardPeriod[] values = values(); + int nextOrdinal = (this.ordinal() + 1) % values.length; + return values[nextOrdinal]; + } + + public ZombiesLeaderboardPeriod previous() { + ZombiesLeaderboardPeriod[] values = values(); + int prevOrdinal = (this.ordinal() - 1 + values.length) % values.length; + return values[prevOrdinal]; + } + + public static ZombiesLeaderboardPeriod fromKey(String key) { + for (ZombiesLeaderboardPeriod period : values()) { + if (period.key.equalsIgnoreCase(key)) { + return period; + } + } + return LIFETIME; + } +} diff --git a/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardPreferences.java b/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardPreferences.java new file mode 100644 index 000000000..a4fb96409 --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardPreferences.java @@ -0,0 +1,17 @@ +package net.swofty.commons.zombies; + +public record ZombiesLeaderboardPreferences( + ZombiesLeaderboardPeriod period, + ZombiesLeaderboardMode mode, + ZombiesLeaderboardView view, + ZombiesTextAlignment textAlignment +) { + public static ZombiesLeaderboardPreferences defaults() { + return new ZombiesLeaderboardPreferences( + ZombiesLeaderboardPeriod.WEEKLY, + ZombiesLeaderboardMode.ALL, + ZombiesLeaderboardView.TOP_10, + ZombiesTextAlignment.CENTER + ); + } +} diff --git a/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardView.java b/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardView.java new file mode 100644 index 000000000..f81cbd963 --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/ZombiesLeaderboardView.java @@ -0,0 +1,38 @@ +package net.swofty.commons.zombies; + +import lombok.Getter; + +@Getter +public enum ZombiesLeaderboardView { + TOP_10("top_10", "Top 10"), + PLAYERS_AROUND_YOU("around_you", "Players Around You"); + + private final String key; + private final String displayName; + + ZombiesLeaderboardView(String key, String displayName) { + this.key = key; + this.displayName = displayName; + } + + public ZombiesLeaderboardView next() { + ZombiesLeaderboardView[] values = values(); + int nextOrdinal = (this.ordinal() + 1) % values.length; + return values[nextOrdinal]; + } + + public ZombiesLeaderboardView previous() { + ZombiesLeaderboardView[] values = values(); + int prevOrdinal = (this.ordinal() - 1 + values.length) % values.length; + return values[prevOrdinal]; + } + + public static ZombiesLeaderboardView fromKey(String key) { + for (ZombiesLeaderboardView view : values()) { + if (view.key.equalsIgnoreCase(key)) { + return view; + } + } + return TOP_10; + } +} diff --git a/commons/src/main/java/net/swofty/commons/zombies/ZombiesMap.java b/commons/src/main/java/net/swofty/commons/zombies/ZombiesMap.java new file mode 100644 index 000000000..c600f091b --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/ZombiesMap.java @@ -0,0 +1,58 @@ +package net.swofty.commons.zombies; + +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +@Getter +public enum ZombiesMap { + DEAD_END(0, "Dead End", 30, "The original Zombies map. Stranded in the middle of a city, make your way through the high rises to battle the undead!"), + BAD_BLOOD(1, "Bad Blood", 30, "After crashing into the courtyard of a beautiful mansion, fend off zombies as you uncover the secrets hidden within!"), + ALIEN_ARCADIUM(2, "Alien Arcadium", 105, "Extraterrestrial Zombies have attacked! Battle alien creatures and save the theme park from destruction."), + PRISON(3, "Prison", 30, "The cells of the undead have been opened! Save yourselves and escape from the Prison!"); + + private final int id; + private final String displayName; + private final int maxRounds; + private final String description; + + ZombiesMap(int id, String displayName, int maxRounds, String description) { + this.id = id; + this.displayName = displayName; + this.maxRounds = maxRounds; + this.description = description; + } + + public boolean isExtendedMap() { + return this == ALIEN_ARCADIUM; + } + + @Nullable + public static ZombiesMap from(String field) { + for (ZombiesMap map : values()) { + if (map.name().equalsIgnoreCase(field)) { + return map; + } + } + return null; + } + + @Nullable + public static ZombiesMap fromDisplayName(String displayName) { + for (ZombiesMap map : values()) { + if (map.displayName.equalsIgnoreCase(displayName)) { + return map; + } + } + return null; + } + + @Nullable + public static ZombiesMap fromId(int id) { + for (ZombiesMap map : values()) { + if (map.id == id) { + return map; + } + } + return null; + } +} diff --git a/commons/src/main/java/net/swofty/commons/zombies/ZombiesModeStats.java b/commons/src/main/java/net/swofty/commons/zombies/ZombiesModeStats.java new file mode 100644 index 000000000..8b6957333 --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/ZombiesModeStats.java @@ -0,0 +1,228 @@ +package net.swofty.commons.zombies; + +import lombok.Getter; +import lombok.Setter; + +import java.time.DayOfWeek; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAdjusters; +import java.util.HashMap; +import java.util.Map; + +@Getter +public class ZombiesModeStats { + + private final Map roundsSurvived; + private final Map wins; + + private final Map bestRound; + private final Map kills; + private final Map playersRevived; + private final Map doorsOpened; + private final Map windowsRepaired; + private final Map timesKnockedDown; + private final Map deaths; + + @Setter private long dailyResetTimestamp; + @Setter private long weeklyResetTimestamp; + @Setter private long monthlyResetTimestamp; + + public ZombiesModeStats() { + this.roundsSurvived = new HashMap<>(); + this.wins = new HashMap<>(); + this.bestRound = new HashMap<>(); + this.kills = new HashMap<>(); + this.playersRevived = new HashMap<>(); + this.doorsOpened = new HashMap<>(); + this.windowsRepaired = new HashMap<>(); + this.timesKnockedDown = new HashMap<>(); + this.deaths = new HashMap<>(); + initializeResetTimestamps(); + } + + public ZombiesModeStats(Map roundsSurvived, Map wins, + Map bestRound, Map kills, Map deaths, + Map playersRevived, Map doorsOpened, + Map windowsRepaired, Map timesKnockedDown, + long dailyResetTimestamp, long weeklyResetTimestamp, long monthlyResetTimestamp) { + this.roundsSurvived = new HashMap<>(roundsSurvived); + this.wins = new HashMap<>(wins); + this.bestRound = new HashMap<>(bestRound); + this.kills = new HashMap<>(kills); + this.deaths = new HashMap<>(deaths); + this.playersRevived = new HashMap<>(playersRevived); + this.doorsOpened = new HashMap<>(doorsOpened); + this.windowsRepaired = new HashMap<>(windowsRepaired); + this.timesKnockedDown = new HashMap<>(timesKnockedDown); + this.dailyResetTimestamp = dailyResetTimestamp; + this.weeklyResetTimestamp = weeklyResetTimestamp; + this.monthlyResetTimestamp = monthlyResetTimestamp; + } + + private void initializeResetTimestamps() { + this.dailyResetTimestamp = computeNextDailyReset(); + this.weeklyResetTimestamp = computeNextWeeklyReset(); + this.monthlyResetTimestamp = computeNextMonthlyReset(); + } + + public static ZombiesModeStats empty() { + return new ZombiesModeStats(); + } + + public void checkAndResetExpiredPeriods() { + long now = System.currentTimeMillis(); + + if (now >= dailyResetTimestamp) { + resetPeriod(ZombiesLeaderboardPeriod.DAILY); + dailyResetTimestamp = computeNextDailyReset(); + } + + if (now >= weeklyResetTimestamp) { + resetPeriod(ZombiesLeaderboardPeriod.WEEKLY); + weeklyResetTimestamp = computeNextWeeklyReset(); + } + + if (now >= monthlyResetTimestamp) { + resetPeriod(ZombiesLeaderboardPeriod.MONTHLY); + monthlyResetTimestamp = computeNextMonthlyReset(); + } + } + + private static long computeNextDailyReset() { + ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + ZonedDateTime nextReset = now.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0); + return nextReset.toInstant().toEpochMilli(); + } + + private static long computeNextWeeklyReset() { + ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + ZonedDateTime nextReset = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)) + .withHour(0).withMinute(0).withSecond(0).withNano(0); + return nextReset.toInstant().toEpochMilli(); + } + + private static long computeNextMonthlyReset() { + ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + ZonedDateTime nextReset = now.with(TemporalAdjusters.firstDayOfNextMonth()) + .withHour(0).withMinute(0).withSecond(0).withNano(0); + return nextReset.toInstant().toEpochMilli(); + } + + private String key(ZombiesLeaderboardMode mode, ZombiesLeaderboardPeriod period) { + return mode.getKey() + ":" + period.getKey(); + } + + // Getters for stats + public long getRoundsSurvived(ZombiesLeaderboardMode mode, ZombiesLeaderboardPeriod period) { + return roundsSurvived.getOrDefault(key(mode, period), 0L); + } + + public long getWins(ZombiesLeaderboardMode mode, ZombiesLeaderboardPeriod period) { + return wins.getOrDefault(key(mode, period), 0L); + } + + public long getKills(ZombiesLeaderboardMode mode, ZombiesLeaderboardPeriod period) { + return kills.getOrDefault(key(mode, period), 0L); + } + + public long getDeaths(ZombiesLeaderboardMode mode, ZombiesLeaderboardPeriod period) { + return deaths.getOrDefault(key(mode, period), 0L); + } + + public long getPlayersRevived(ZombiesLeaderboardMode mode, ZombiesLeaderboardPeriod period) { + return playersRevived.getOrDefault(key(mode, period), 0L); + } + + public long getDoorsOpened(ZombiesLeaderboardMode mode, ZombiesLeaderboardPeriod period) { + return doorsOpened.getOrDefault(key(mode, period), 0L); + } + + public long getWindowsRepaired(ZombiesLeaderboardMode mode, ZombiesLeaderboardPeriod period) { + return windowsRepaired.getOrDefault(key(mode, period), 0L); + } + + public long getTimesKnockedDown(ZombiesLeaderboardMode mode, ZombiesLeaderboardPeriod period) { + return timesKnockedDown.getOrDefault(key(mode, period), 0L); + } + + public long getBestRound(ZombiesLeaderboardMode mode) { + return bestRound.getOrDefault(mode.getKey(), 0L); + } + + // Record methods (for all periods) + public void recordWin(ZombiesLeaderboardMode mode) { + for (ZombiesLeaderboardPeriod period : ZombiesLeaderboardPeriod.values()) { + wins.merge(key(mode, period), 1L, Long::sum); + } + } + + public void recordKill(ZombiesLeaderboardMode mode) { + for (ZombiesLeaderboardPeriod period : ZombiesLeaderboardPeriod.values()) { + kills.merge(key(mode, period), 1L, Long::sum); + } + } + + public void recordDeath(ZombiesLeaderboardMode mode) { + for (ZombiesLeaderboardPeriod period : ZombiesLeaderboardPeriod.values()) { + deaths.merge(key(mode, period), 1L, Long::sum); + } + } + + public void recordRevive(ZombiesLeaderboardMode mode) { + for (ZombiesLeaderboardPeriod period : ZombiesLeaderboardPeriod.values()) { + playersRevived.merge(key(mode, period), 1L, Long::sum); + } + } + + public void recordDoorOpened(ZombiesLeaderboardMode mode) { + for (ZombiesLeaderboardPeriod period : ZombiesLeaderboardPeriod.values()) { + doorsOpened.merge(key(mode, period), 1L, Long::sum); + } + } + + public void recordWindowRepaired(ZombiesLeaderboardMode mode) { + for (ZombiesLeaderboardPeriod period : ZombiesLeaderboardPeriod.values()) { + windowsRepaired.merge(key(mode, period), 1L, Long::sum); + } + } + + public void recordTimedKnockdown(ZombiesLeaderboardMode mode) { + for (ZombiesLeaderboardPeriod period : ZombiesLeaderboardPeriod.values()) { + timesKnockedDown.merge(key(mode, period), 1L, Long::sum); + } + } + + public void recordRoundsSurvived(ZombiesLeaderboardMode mode, long rounds) { + for (ZombiesLeaderboardPeriod period : ZombiesLeaderboardPeriod.values()) { + roundsSurvived.merge(key(mode, period), rounds, Long::sum); + } + } + + public void updateBestRound(ZombiesLeaderboardMode mode, long round) { + long current = getBestRound(mode); + if (round > current) { + bestRound.put(mode.getKey(), round); + } + } + + public void resetPeriod(ZombiesLeaderboardPeriod period) { + for (ZombiesLeaderboardMode mode : ZombiesLeaderboardMode.values()) { + String k = key(mode, period); + roundsSurvived.remove(k); + wins.remove(k); + kills.remove(k); + deaths.remove(k); + playersRevived.remove(k); + doorsOpened.remove(k); + windowsRepaired.remove(k); + timesKnockedDown.remove(k); + } + } + + public ZombiesModeStats copy() { + return new ZombiesModeStats(roundsSurvived, wins, bestRound, kills, deaths, + playersRevived, doorsOpened, windowsRepaired, timesKnockedDown, + dailyResetTimestamp, weeklyResetTimestamp, monthlyResetTimestamp); + } +} diff --git a/commons/src/main/java/net/swofty/commons/zombies/ZombiesStatType.java b/commons/src/main/java/net/swofty/commons/zombies/ZombiesStatType.java new file mode 100644 index 000000000..fe95e3bf4 --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/ZombiesStatType.java @@ -0,0 +1,35 @@ +package net.swofty.commons.zombies; + +import lombok.Getter; + +@Getter +public enum ZombiesStatType { + ROUNDS_SURVIVED("rounds_survived", "Rounds Survived", false), + WINS("wins", "Wins", false), + BEST_ROUND("best_round", "Best Round", false), + KILLS("kills", "Kills", false), + PLAYERS_REVIVED("players_revived", "Players Revived", false), + DOORS_OPENED("doors_opened", "Doors Opened", false), + WINDOWS_REPAIRED("windows_repaired", "Windows Repaired", false), + TIMES_KNOCKED_DOWN("times_knocked_down", "Times Knocked Down", false), + DEATHS("deaths", "Deaths", false); + + private final String key; + private final String displayName; + private final boolean timedTracking; + + ZombiesStatType(String key, String displayName, boolean timedTracking) { + this.key = key; + this.displayName = displayName; + this.timedTracking = timedTracking; + } + + public static ZombiesStatType fromKey(String key) { + for (ZombiesStatType type : values()) { + if (type.key.equalsIgnoreCase(key)) { + return type; + } + } + return WINS; + } +} diff --git a/commons/src/main/java/net/swofty/commons/zombies/ZombiesTextAlignment.java b/commons/src/main/java/net/swofty/commons/zombies/ZombiesTextAlignment.java new file mode 100644 index 000000000..689d1db1e --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/ZombiesTextAlignment.java @@ -0,0 +1,38 @@ +package net.swofty.commons.zombies; + +import lombok.Getter; + +@Getter +public enum ZombiesTextAlignment { + CENTER("center", "Center"), + BLOCK("block", "Block"); + + private final String key; + private final String displayName; + + ZombiesTextAlignment(String key, String displayName) { + this.key = key; + this.displayName = displayName; + } + + public ZombiesTextAlignment next() { + ZombiesTextAlignment[] values = values(); + int nextOrdinal = (this.ordinal() + 1) % values.length; + return values[nextOrdinal]; + } + + public ZombiesTextAlignment previous() { + ZombiesTextAlignment[] values = values(); + int prevOrdinal = (this.ordinal() - 1 + values.length) % values.length; + return values[prevOrdinal]; + } + + public static ZombiesTextAlignment fromKey(String key) { + for (ZombiesTextAlignment alignment : values()) { + if (alignment.key.equalsIgnoreCase(key)) { + return alignment; + } + } + return CENTER; + } +} diff --git a/commons/src/main/java/net/swofty/commons/zombies/map/ZombiesMapsConfig.java b/commons/src/main/java/net/swofty/commons/zombies/map/ZombiesMapsConfig.java new file mode 100644 index 000000000..2189ebbff --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/zombies/map/ZombiesMapsConfig.java @@ -0,0 +1,181 @@ +package net.swofty.commons.zombies.map; + +import lombok.Getter; +import lombok.Setter; +import net.swofty.commons.zombies.ZombiesMap; + +import java.util.List; +import java.util.Map; + +@Getter +@Setter +@SuppressWarnings("unused") +public class ZombiesMapsConfig { + private List maps; + + @Getter + @Setter + public static class MapEntry { + private String id; + private String name; + private ZombiesMap mapType; + private MapConfiguration configuration; + + @Getter + @Setter + public static class MapConfiguration { + private MapBounds bounds; + private MapLocations locations; + private Map windows; + private Map doors; + private Map spawners; + private Map bossSpawns; + private Map shops; + private Map machines; + private List weaponChests; + private List teamMachines; + private Position statsHologram; + + @Getter + @Setter + public static class MapLocations { + private PitchYawPosition spawn; + private PitchYawPosition spectator; + private Position practiceZombie; + } + + @Getter + @Setter + public static class MapBounds { + private MinMax x; + private MinMax y; + private MinMax z; + } + + @Getter + @Setter + public static class Window { + private String name; + private List blockPositions; + private Area zombieArea; + private Area playerArea; + private List connectedSpawners; + + @Getter + @Setter + public static class Area { + private Position pos1; + private Position pos2; + } + } + + @Getter + @Setter + public static class Door { + private String name; + private int goldCost; + private Area area; + private List connectedDoors; + private List connectedSpawners; + private Integer openOnRound; + private boolean isPowerSwitchDoor; + private String fakeDoorName; + + @Getter + @Setter + public static class Area { + private Position pos1; + private Position pos2; + } + } + + @Getter + @Setter + public static class Spawner { + private String name; + private Position location; + private boolean isDefault; + private String requiredDoor; + private List connectedWindows; + } + + @Getter + @Setter + public static class BossSpawn { + private String name; + private Position location; + private String zombieType; + private boolean isDefault; + } + + @Getter + @Setter + public static class Shop { + private ShopType type; + private Position location; + private int goldCost; + private ArmorShopConfig armorConfig; + private WeaponShopConfig weaponConfig; + private PerkShopConfig perkConfig; + + @Getter + @Setter + public static class ArmorShopConfig { + private ArmorType armorType; + private ArmorPart armorPart; + + public enum ArmorType { + LEATHER, GOLD, IRON, DIAMOND + } + + public enum ArmorPart { + UP, DOWN + } + } + + @Getter + @Setter + public static class WeaponShopConfig { + private String weaponName; + private int weaponGold; + private int ammoGold; + } + + @Getter + @Setter + public static class PerkShopConfig { + private String perkName; + } + } + + @Getter + @Setter + public static class Machine { + private MachineType type; + private Position location; + private int goldCost; + + public enum MachineType { + ULTIMATE_MACHINE, + POWER_SWITCH, + TEAM_MACHINE + } + } + } + } + + public record Position(double x, double y, double z) { + } + + public record PitchYawPosition(double x, double y, double z, float pitch, float yaw) { + } + + public record MinMax(double min, double max) { + } + + public enum ShopType { + ARMOR, + WEAPON, + PERK + } +} diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts index 63428c77b..5348961cb 100644 --- a/loader/build.gradle.kts +++ b/loader/build.gradle.kts @@ -54,6 +54,9 @@ dependencies { implementation(project(":type.skywarsgame")) implementation(project(":type.skywarsconfigurator")) + implementation(project(":type.arcadelobby")) + implementation(project(":type.zombiesconfigurator")) + implementation(project(":type.generic")) implementation(project(":commons")) implementation(project(":proxy.api")) diff --git a/settings.gradle.kts b/settings.gradle.kts index c8c44900c..41b60acbd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,6 +38,8 @@ include(":type.murdermysteryconfigurator") include(":type.skywarslobby") include(":type.skywarsgame") include(":type.skywarsconfigurator") +include(":type.arcadelobby") +include(":type.zombiesconfigurator") include(":service.auctionhouse") include(":service.bazaar") diff --git a/type.arcadelobby/build.gradle.kts b/type.arcadelobby/build.gradle.kts new file mode 100644 index 000000000..fe7d49120 --- /dev/null +++ b/type.arcadelobby/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + java +} + +group = "net.swofty" +version = "3.0" + +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 + toolchain { + languageVersion.set(JavaLanguageVersion.of(25)) + } +} + +repositories { + maven("https://jitpack.io") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") + maven("https://repo.viaversion.com") +} + +dependencies { + implementation(project(":type.lobby")) + implementation(project(":type.generic")) + implementation(project(":commons")) + implementation(project(":proxy.api")) + implementation("org.mongodb:bson:4.11.2") + implementation("net.kyori:adventure-text-minimessage:4.25.0") + compileOnly("net.minestom:minestom:2025.12.20c-1.21.11") { + exclude(group = "org.jboss.shrinkwrap.resolver", module = "shrinkwrap-resolver-depchain") + } + implementation("org.tinylog:tinylog-api:2.7.0") + implementation("org.tinylog:tinylog-impl:2.7.0") +} \ No newline at end of file diff --git a/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/ArcadeLobbyScoreboard.java b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/ArcadeLobbyScoreboard.java new file mode 100644 index 000000000..a8cd95e10 --- /dev/null +++ b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/ArcadeLobbyScoreboard.java @@ -0,0 +1,91 @@ +package net.swofty.type.arcadelobby; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.Player; +import net.minestom.server.timer.Scheduler; +import net.minestom.server.timer.TaskSchedule; +import net.swofty.commons.StringUtility; +import net.swofty.type.generic.HypixelConst; +import net.swofty.type.generic.HypixelGenericLoader; +import net.swofty.type.generic.data.HypixelDataHandler; +import net.swofty.type.generic.data.datapoints.DatapointLeaderboardLong; +import net.swofty.type.generic.data.handlers.ArcadeDataHandler; +import net.swofty.type.generic.scoreboard.HypixelScoreboard; +import net.swofty.type.generic.user.HypixelPlayer; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class ArcadeLobbyScoreboard { + + private static final HypixelScoreboard scoreboard = new HypixelScoreboard(); + private static Integer prototypeName = 0; + + public static void start() { + Scheduler scheduler = MinecraftServer.getSchedulerManager(); + + scheduler.submitTask(() -> { + prototypeName++; + if (prototypeName > 50) { + prototypeName = 0; + } + + for (HypixelPlayer player : HypixelGenericLoader.getLoadedPlayers()) { + HypixelDataHandler dataHandler = player.getDataHandler(); + ArcadeDataHandler arcadeDataHandler = ArcadeDataHandler.getUser(player); + + if (dataHandler == null || arcadeDataHandler == null) { + continue; + } + + long wins = arcadeDataHandler.get(ArcadeDataHandler.Data.TOTAL_WINS, DatapointLeaderboardLong.class).getValue(); + long coins = arcadeDataHandler.get(ArcadeDataHandler.Data.COINS, DatapointLeaderboardLong.class).getValue(); + + List lines = new ArrayList<>(); + lines.add("§7" + new SimpleDateFormat("MM/dd/yy").format(new Date()) + " §8" + HypixelConst.getServerName()); + lines.add("§7 "); + lines.add("§fCheck out your"); + lines.add("§fArcade Games"); + lines.add("§fProfile at the"); + lines.add("§fStats NPC!"); + lines.add("§7 "); + lines.add("§fTotal Wins: §a" + wins); + lines.add("§fCoins: §6" + StringUtility.commaify(coins)); + lines.add("§7 "); + lines.add("§ewww.hypixel.net"); + + if (!scoreboard.hasScoreboard(player)) { + scoreboard.createScoreboard(player, getSidebarName(prototypeName)); + } + + scoreboard.updateLines(player, lines); + scoreboard.updateTitle(player, getSidebarName(prototypeName)); + } + return TaskSchedule.tick(5); + }); + } + + public static void removeCache(Player player) { + scoreboard.removeScoreboard(player); + } + + private static String getSidebarName(int counter) { + String baseText = "ARCADE GAMES"; + String[] colors = {"§f§l", "§6§l", "§e§l"}; + String endColor = "§a§l"; + + if (counter > 0 && counter <= baseText.length()) { + return colors[0] + baseText.substring(0, counter - 1) + + colors[1] + baseText.charAt(counter - 1) + + colors[2] + baseText.substring(counter) + + endColor; + } else if ((counter >= 9 && counter <= 19) || + (counter >= 25 && counter <= 29)) { + return colors[0] + baseText + endColor; + } else { + return colors[2] + baseText + endColor; + } + } +} diff --git a/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/TypeArcadeLobbyLoader.java b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/TypeArcadeLobbyLoader.java new file mode 100644 index 000000000..972e45217 --- /dev/null +++ b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/TypeArcadeLobbyLoader.java @@ -0,0 +1,186 @@ +package net.swofty.type.arcadelobby; + +import lombok.Getter; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.timer.TaskSchedule; +import net.swofty.commons.CustomWorlds; +import net.swofty.commons.ServerType; +import net.swofty.commons.ServiceType; +import net.swofty.proxyapi.redis.ProxyToClient; +import net.swofty.proxyapi.redis.ServiceToClient; +import net.swofty.type.arcadelobby.impl.Collectibles; +import net.swofty.type.arcadelobby.impl.ShopsCosmetics; +import net.swofty.type.arcadelobby.launchpad.ArcadeLaunchPads; +import net.swofty.type.arcadelobby.tab.tab.ArcadePlayersOnlineModule; +import net.swofty.type.generic.HypixelGenericLoader; +import net.swofty.type.generic.command.HypixelCommand; +import net.swofty.type.generic.data.GameDataHandler; +import net.swofty.type.generic.data.handlers.ArcadeDataHandler; +import net.swofty.type.generic.entity.hologram.PlayerHolograms; +import net.swofty.type.generic.entity.npc.HypixelNPC; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.tab.TablistManager; +import net.swofty.type.generic.tab.TablistModule; +import net.swofty.type.lobby.LobbyTypeLoader; +import net.swofty.type.lobby.events.*; +import net.swofty.type.lobby.item.LobbyItem; +import net.swofty.type.lobby.item.LobbyItemHandler; +import net.swofty.type.lobby.item.impl.HidePlayers; +import net.swofty.type.lobby.item.impl.LobbySelector; +import net.swofty.type.lobby.item.impl.PlayCompass; +import net.swofty.type.lobby.item.impl.ProfileItem; +import net.swofty.type.lobby.launchpad.LaunchPad; +import net.swofty.type.lobby.launchpad.LaunchPadHandler; +import net.swofty.type.lobby.parkour.LobbyParkourManager; +import org.jetbrains.annotations.Nullable; +import org.tinylog.Logger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class TypeArcadeLobbyLoader implements LobbyTypeLoader { + + private final Pos spawnPoint = new Pos(-18.5, 67, 0.5, -90, 0); + + @Getter + private final LobbyItemHandler itemHandler = new LobbyItemHandler(); + + @Override + public ServerType getType() { + return ServerType.ARCADE_LOBBY; + } + + @Override + public void onInitialize(MinecraftServer server) { + } + + @Override + public void afterInitialize(MinecraftServer server) { + ArcadeLobbyScoreboard.start(); + LaunchPadHandler.register(MinecraftServer.getSchedulerManager(), getLaunchPads()); + + getHotbarItems().values().forEach(itemHandler::add); + + HypixelGenericLoader.loopThroughPackage("net.swofty.type.arcadelobby.commands", HypixelCommand.class).forEach(command -> { + try { + MinecraftServer.getCommandManager().register(command.getCommand()); + } catch (Exception e) { + Logger.error(e, "Failed to register command " + command.getCommand().getName() + " in class " + command.getClass().getSimpleName()); + } + }); + + // Schedule hologram updates every 2 seconds + MinecraftServer.getSchedulerManager().buildTask(PlayerHolograms::updateExternalHolograms).delay(TaskSchedule.seconds(5)) + .repeat(TaskSchedule.seconds(2)) + .schedule(); + + LobbyTypeLoader.registerLobbyCommands(); + } + + @Override + public List getLaunchPads() { + return Arrays.asList(ArcadeLaunchPads.values()); + } + + @Override + public @Nullable LobbyParkourManager getParkourManager() { + return null; + } + + @Override + public Map getHotbarItems() { + return Map.of( + 0, new PlayCompass(), + 1, new ProfileItem(), + 2, new ShopsCosmetics(), + 4, new Collectibles(), + 7, new HidePlayers(), + 8, new LobbySelector() + ); + } + + @Override + public List getRequiredServices() { + return List.of(ServiceType.ORCHESTRATOR); + } + + @Override + public TablistManager getTablistManager() { + return new TablistManager() { + @Override + public List getModules() { + return List.of( + new ArcadePlayersOnlineModule(1), + new ArcadePlayersOnlineModule(2), + new ArcadePlayersOnlineModule(3), + new ArcadePlayersOnlineModule(4) + ); + } + }; + } + + @Override + public LoaderValues getLoaderValues() { + return new LoaderValues( + (type) -> spawnPoint, + false + ); + } + + @Override + public List getTraditionalEvents() { + List events = new ArrayList<>(HypixelGenericLoader.loopThroughPackage( + "net.swofty.type.arcadelobby.events", + HypixelEventClass.class + ).toList()); + events.add(new LobbyItemEvents()); + events.add(new LobbyParkourEvents()); + events.add(new LobbyLaunchPadEvents()); + events.add(new LobbyPlayerJoinEvents()); + events.add(new LobbyBlockBreak()); + events.add(new LobbyPlayerMove(spawnPoint)); + return events; + } + + @Override + public List getCustomEvents() { + return HypixelGenericLoader.loopThroughPackage( + "net.swofty.type.arcadelobby.events.custom", + HypixelEventClass.class + ).toList(); + } + + @Override + public List getNPCs() { + return HypixelGenericLoader.loopThroughPackage( + "net.swofty.type.arcadelobby.npcs", + HypixelNPC.class + ).toList(); + } + + @Override + public List getServiceRedisListeners() { + return HypixelGenericLoader.loopThroughPackage( + "net.swofty.type.arcadelobby.redis.service", + ServiceToClient.class + ).toList(); + } + + @Override + public List getProxyRedisListeners() { + return List.of(); + } + + @Override + public @Nullable CustomWorlds getMainInstance() { + return CustomWorlds.ARCADE_LOBBY; + } + + @Override + public List> getAdditionalDataHandlers() { + return List.of(ArcadeDataHandler.class); + } +} diff --git a/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/impl/Collectibles.java b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/impl/Collectibles.java new file mode 100644 index 000000000..0fdc5495e --- /dev/null +++ b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/impl/Collectibles.java @@ -0,0 +1,36 @@ +package net.swofty.type.arcadelobby.impl; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.minestom.server.event.item.ItemDropEvent; +import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.lobby.item.LobbyItem; + +public class Collectibles extends LobbyItem { + + public Collectibles() { + super("collectibles"); + } + + @Override + public ItemStack getBlandItem() { + return ItemStackCreator.createNamedItemStack(Material.CHEST, "§aCollectibles §7(Right Click)").build(); + } + + @Override + public void onItemDrop(ItemDropEvent event) { + event.setCancelled(true); + } + + @Override + public void onItemInteract(PlayerInstanceEvent event) { + ((CancellableEvent) event).setCancelled(true); + event.getPlayer().sendMessage(Component.text("§cThis Feature is not there yet. §aOpen a Pull request HERE to get it added quickly!") + .clickEvent(ClickEvent.openUrl("https://github.com/Swofty-Developments/HypixelSkyBlock"))); + } + +} diff --git a/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/impl/ShopsCosmetics.java b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/impl/ShopsCosmetics.java new file mode 100644 index 000000000..d1cadf0e1 --- /dev/null +++ b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/impl/ShopsCosmetics.java @@ -0,0 +1,36 @@ +package net.swofty.type.arcadelobby.impl; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.minestom.server.event.item.ItemDropEvent; +import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.lobby.item.LobbyItem; + +public class ShopsCosmetics extends LobbyItem { + + public ShopsCosmetics() { + super("shops_cosmetics_menu"); + } + + @Override + public ItemStack getBlandItem() { + return ItemStackCreator.createNamedItemStack(Material.EMERALD, "§aShops & Cosmetics §7(Right Click)").build(); + } + + @Override + public void onItemDrop(ItemDropEvent event) { + event.setCancelled(true); + } + + @Override + public void onItemInteract(PlayerInstanceEvent event) { + ((CancellableEvent) event).setCancelled(true); + event.getPlayer().sendMessage(Component.text("§cThis Feature is not there yet. §aOpen a Pull request HERE to get it added quickly!") + .clickEvent(ClickEvent.openUrl("https://github.com/Swofty-Developments/HypixelSkyBlock"))); + } + +} diff --git a/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/launchpad/ArcadeLaunchPads.java b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/launchpad/ArcadeLaunchPads.java new file mode 100644 index 000000000..ad197de92 --- /dev/null +++ b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/launchpad/ArcadeLaunchPads.java @@ -0,0 +1,31 @@ +package net.swofty.type.arcadelobby.launchpad; + +import lombok.Getter; +import net.minestom.server.coordinate.Pos; +import net.swofty.type.generic.user.HypixelPlayer; +import net.swofty.type.lobby.launchpad.LaunchPad; +import net.swofty.type.lobby.launchpad.LaunchPadHandler; + +import java.util.List; +import java.util.function.Consumer; + +@Getter +public enum ArcadeLaunchPads implements LaunchPad { + SPAWN(LaunchPadHandler.getSlimeBlocksNear(new Pos(-14, 66, 0)), + new Pos(1.5, 62, 0.5), + (player) -> {}, + ""); + ; + + private final List slimeBlocks; + private final Pos destination; + private final Consumer afterFinished; + private final String rejectionMessage; + + ArcadeLaunchPads(List slimeBlocks, Pos destination, Consumer afterFinished, String rejectionMessage) { + this.slimeBlocks = slimeBlocks; + this.destination = destination; + this.afterFinished = afterFinished; + this.rejectionMessage = rejectionMessage; + } +} diff --git a/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/tab/tab/ArcadePlayersOnlineModule.java b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/tab/tab/ArcadePlayersOnlineModule.java new file mode 100644 index 000000000..508b8970c --- /dev/null +++ b/type.arcadelobby/src/main/java/net/swofty/type/arcadelobby/tab/tab/ArcadePlayersOnlineModule.java @@ -0,0 +1,63 @@ +package net.swofty.type.arcadelobby.tab.tab; + +import net.swofty.commons.StringUtility; +import net.swofty.type.generic.HypixelGenericLoader; +import net.swofty.type.generic.data.HypixelDataHandler; +import net.swofty.type.generic.data.datapoints.DatapointRank; +import net.swofty.type.generic.tab.TablistModule; +import net.swofty.type.generic.tab.TablistSkinRegistry; +import net.swofty.type.generic.user.HypixelPlayer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ArcadePlayersOnlineModule extends TablistModule { + public int page; + + public ArcadePlayersOnlineModule(int page) { + this.page = page; + } + + @Override + public List getEntries(HypixelPlayer player) { + List players = HypixelGenericLoader.getLoadedPlayers(); + + ArrayList entries = new ArrayList<>(List.of( + new TablistEntry(getCentered("§a§lPlayers §f(" + players.size() + ")"), TablistSkinRegistry.GREEN) + )); + + List toShow = new ArrayList<>(); + + // Sort players by their rank ordinal in reverse + players.sort((o1, o2) -> o2.getDataHandler().get(HypixelDataHandler.Data.RANK, DatapointRank.class).getValue().ordinal() + - o1.getDataHandler().get(HypixelDataHandler.Data.RANK, DatapointRank.class).getValue().ordinal()); + Collections.reverse(players); + + // In chunks of 19, load the players into the toShow list. + // If the page is 1, then use the first 19, if the page is 2, then use the second set of 19, etc. + for (int i = 0; i < players.size(); i++) { + if (i >= (page - 1) * 19 && i < page * 19) { + toShow.add(players.get(i)); + } + } + + for (int x = 0; x < 19; x++) { + if (x >= toShow.size()) { + entries.add(new TablistEntry(" ", TablistSkinRegistry.GRAY)); + continue; + } + + HypixelPlayer tablistPlayer = toShow.get(x); + String displayName = getFullDisplayName(tablistPlayer); + + entries.add(new TablistEntry(displayName, TablistSkinRegistry.GRAY)); + } + + return entries; + } + + private String getFullDisplayName(HypixelPlayer player) { + return player.getRank().getPrefix() + StringUtility.getTextFromComponent(player.getName()); + } +} diff --git a/type.generic/src/main/java/net/swofty/type/generic/HypixelGenericLoader.java b/type.generic/src/main/java/net/swofty/type/generic/HypixelGenericLoader.java index 191df7ec2..698a90d58 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/HypixelGenericLoader.java +++ b/type.generic/src/main/java/net/swofty/type/generic/HypixelGenericLoader.java @@ -26,10 +26,7 @@ import net.swofty.type.generic.command.HypixelCommand; import net.swofty.type.generic.data.GameDataHandlerRegistry; import net.swofty.type.generic.data.HypixelDataHandler; -import net.swofty.type.generic.data.handlers.BedWarsDataHandler; -import net.swofty.type.generic.data.handlers.MurderMysteryDataHandler; -import net.swofty.type.generic.data.handlers.PrototypeLobbyDataHandler; -import net.swofty.type.generic.data.handlers.SkywarsDataHandler; +import net.swofty.type.generic.data.handlers.*; import net.swofty.type.generic.data.mongodb.AttributeDatabase; import net.swofty.type.generic.data.mongodb.AuthenticationDatabase; import net.swofty.type.generic.data.mongodb.BedWarsStatsDatabase; @@ -204,6 +201,7 @@ public void initialize(MinecraftServer server) { GameDataHandlerRegistry.register(new PrototypeLobbyDataHandler()); GameDataHandlerRegistry.register(new MurderMysteryDataHandler()); GameDataHandlerRegistry.register(new SkywarsDataHandler()); + GameDataHandlerRegistry.register(new ArcadeDataHandler()); // Register Block Handlers MinecraftServer.getBlockManager().registerHandler(PlayerHeadBlockHandler.KEY, PlayerHeadBlockHandler::new); diff --git a/type.generic/src/main/java/net/swofty/type/generic/data/datapoints/DatapointZombiesModeStats.java b/type.generic/src/main/java/net/swofty/type/generic/data/datapoints/DatapointZombiesModeStats.java new file mode 100644 index 000000000..2f89d21da --- /dev/null +++ b/type.generic/src/main/java/net/swofty/type/generic/data/datapoints/DatapointZombiesModeStats.java @@ -0,0 +1,112 @@ +package net.swofty.type.generic.data.datapoints; + +import net.swofty.commons.protocol.Serializer; +import net.swofty.commons.zombies.ZombiesLeaderboardMode; +import net.swofty.commons.zombies.ZombiesLeaderboardPeriod; +import net.swofty.commons.zombies.ZombiesModeStats; +import net.swofty.type.generic.data.Datapoint; +import net.swofty.type.generic.leaderboard.MapLeaderboardTracked; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +public class DatapointZombiesModeStats extends Datapoint implements MapLeaderboardTracked { + + private static final String LEADERBOARD_PREFIX = "zombies"; + + public DatapointZombiesModeStats(String key, ZombiesModeStats value) { + super(key, value, new Serializer<>() { + @Override + public String serialize(ZombiesModeStats value) { + JSONObject json = new JSONObject(); + + json.put("roundsSurvived", value.getRoundsSurvived()); + json.put("wins", value.getWins()); + json.put("bestRound", value.getBestRound()); + json.put("kills", value.getKills()); + json.put("playersRevived", value.getPlayersRevived()); + json.put("doorsOpened", value.getDoorsOpened()); + json.put("windowsRepaired", value.getWindowsRepaired()); + json.put("timesKnockedDown", value.getTimesKnockedDown()); + json.put("deaths", value.getDeaths()); + + json.put("dailyReset", value.getDailyResetTimestamp()); + json.put("weeklyReset", value.getWeeklyResetTimestamp()); + json.put("monthlyReset", value.getMonthlyResetTimestamp()); + return json.toString(); + } + + @Override + public ZombiesModeStats deserialize(String json) { + if (json == null || json.isEmpty()) { + return ZombiesModeStats.empty(); + } + + JSONObject obj = new JSONObject(json); + + Map roundsSurvived = parseMap(obj.optJSONObject("roundsSurvived")); + Map wins = parseMap(obj.optJSONObject("wins")); + Map bestRound = parseMap(obj.optJSONObject("bestRound")); + Map kills = parseMap(obj.optJSONObject("kills")); + Map playersRevived = parseMap(obj.optJSONObject("playersRevived")); + Map doorsOpened = parseMap(obj.optJSONObject("doorsOpened")); + Map windowsRepaired = parseMap(obj.optJSONObject("windowsRepaired")); + Map timesKnockedDown = parseMap(obj.optJSONObject("timesKnockedDown")); + Map deaths = parseMap(obj.optJSONObject("deaths")); + + long dailyReset = obj.optLong("dailyReset", 0); + long weeklyReset = obj.optLong("weeklyReset", 0); + long monthlyReset = obj.optLong("monthlyReset", 0); + + ZombiesModeStats stats = new ZombiesModeStats( + roundsSurvived, wins, bestRound, kills, playersRevived, + doorsOpened, windowsRepaired, timesKnockedDown, deaths, + dailyReset, weeklyReset, monthlyReset); + stats.checkAndResetExpiredPeriods(); + return stats; + } + + private Map parseMap(JSONObject obj) { + Map map = new HashMap<>(); + if (obj != null) { + for (String key : obj.keySet()) { + map.put(key, obj.getLong(key)); + } + } + return map; + } + + @Override + public ZombiesModeStats clone(ZombiesModeStats value) { + return value.copy(); + } + }); + } + + public DatapointZombiesModeStats(String key) { + this(key, ZombiesModeStats.empty()); + } + + @Override + public String getLeaderboardPrefix() { + return LEADERBOARD_PREFIX; + } + + @Override + public Map getAllLeaderboardScores() { + Map scores = new HashMap<>(); + ZombiesModeStats stats = getValue(); + if (stats == null) return scores; + + for (ZombiesLeaderboardMode mode : ZombiesLeaderboardMode.values()) { + for (ZombiesLeaderboardPeriod period : ZombiesLeaderboardPeriod.values()) { + String suffix = mode.getKey() + ":" + period.getKey(); + + scores.put("wins:" + suffix, (double) stats.getWins(mode, period)); + scores.put("rounds_survived:" + suffix, (double) stats.getRoundsSurvived(mode, period)); + } + } + return scores; + } +} diff --git a/type.generic/src/main/java/net/swofty/type/generic/data/handlers/ArcadeDataHandler.java b/type.generic/src/main/java/net/swofty/type/generic/data/handlers/ArcadeDataHandler.java new file mode 100644 index 000000000..ffb1b7ce1 --- /dev/null +++ b/type.generic/src/main/java/net/swofty/type/generic/data/handlers/ArcadeDataHandler.java @@ -0,0 +1,232 @@ +package net.swofty.type.generic.data.handlers; + +import io.sentry.Sentry; +import lombok.Getter; +import net.swofty.type.generic.data.DataHandler; +import net.swofty.type.generic.data.Datapoint; +import net.swofty.type.generic.data.GameDataHandler; +import net.swofty.type.generic.data.datapoints.*; +import net.swofty.type.generic.data.mongodb.UserDatabase; +import net.swofty.type.generic.user.HypixelPlayer; +import org.bson.Document; +import org.jetbrains.annotations.Nullable; +import org.tinylog.Logger; +import tools.jackson.core.JacksonException; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class ArcadeDataHandler extends DataHandler implements GameDataHandler { + public static final Map arcadeCache = new HashMap<>(); + + public ArcadeDataHandler() { super(); } + public ArcadeDataHandler(UUID uuid) { super(uuid); } + + @Override + public String getHandlerId() { + return "arcade"; + } + + @Override + public Map getCache() { + return arcadeCache; + } + + @Override + public DataHandler createFromDocument(UUID playerUuid, Document document) { + ArcadeDataHandler handler = new ArcadeDataHandler(playerUuid); + return handler.fromDocument(document); + } + + @Override + public DataHandler initWithDefaults(UUID playerUuid) { + return initUserWithDefaultData(playerUuid); + } + + @Override + public DataHandler getHandler(UUID playerUuid) { + return arcadeCache.get(playerUuid); + } + + @Override + public void cacheHandler(UUID playerUuid, DataHandler handler) { + arcadeCache.put(playerUuid, (ArcadeDataHandler) handler); + } + + @Override + public void removeFromCache(UUID playerUuid) { + arcadeCache.remove(playerUuid); + } + + public static ArcadeDataHandler getOfOfflinePlayer(UUID uuid) throws RuntimeException { + UserDatabase userDatabase = new UserDatabase(uuid.toString()); + Document doc = userDatabase.getHypixelData(); + return createFromDocument(doc); + } + + @Override + public boolean hasDataInDocument(Document document) { + if (document == null) return false; + for (Data data : Data.values()) { + if (document.containsKey(data.getKey())) { + return true; + } + } + return false; + } + + public static ArcadeDataHandler getUser(UUID uuid) { + if (!arcadeCache.containsKey(uuid)) throw new RuntimeException("User " + uuid + " does not exist!"); + return arcadeCache.get(uuid); + } + + public static @Nullable ArcadeDataHandler getUser(HypixelPlayer player) { + try { return getUser(player.getUuid()); } catch (Exception e) { return null; } + } + + @Override + public ArcadeDataHandler fromDocument(Document document) { + if (document == null) return initUserWithDefaultData(this.uuid); + + if (document.containsKey("_id")) { + this.uuid = UUID.fromString(document.getString("_id")); + } + + for (Data data : Data.values()) { + String key = data.getKey(); + if (!document.containsKey(key)) { + this.datapoints.put(key, data.getDefaultDatapoint().deepClone().setUser(this).setData(data)); + continue; + } + String jsonValue = document.getString(key); + try { + Datapoint dp = data.getDefaultDatapoint().deepClone(); + dp.deserializeValue(jsonValue); + this.datapoints.put(key, dp.setUser(this).setData(data)); + } catch (Exception e) { + this.datapoints.put(key, data.getDefaultDatapoint().deepClone().setUser(this).setData(data)); + Logger.info("Issue with datapoint " + key + " for user " + this.uuid + " - defaulting"); + e.printStackTrace(); + Sentry.captureException(e); + } + } + return this; + } + + public static ArcadeDataHandler createFromDocument(Document document) { + ArcadeDataHandler handler = new ArcadeDataHandler(); + return handler.fromDocument(document); + } + + @Override + public Document toDocument() { + Document document = new Document(); + document.put("_owner", this.uuid.toString()); + for (Data data : Data.values()) { + try { + document.put(data.getKey(), getDatapoint(data.getKey()).getSerializedValue()); + } catch (JacksonException e) { + e.printStackTrace(); + } + } + return document; + } + + public Datapoint get(Data datapoint) { + Datapoint dp = this.datapoints.get(datapoint.key); + return dp != null ? dp : datapoint.defaultDatapoint; + } + + @SuppressWarnings("unchecked") + public > R get(Data datapoint, Class type) { + Datapoint dp = this.datapoints.get(datapoint.key); + return (R) (dp != null ? type.cast(dp) : type.cast(datapoint.defaultDatapoint)); + } + + @Override + public void runOnLoad(HypixelPlayer player) { + for (Data data : Data.values()) { + if (data.onLoad != null) { + data.onLoad.accept(player, get(data)); + } + } + } + + @Override + public void runOnSave(HypixelPlayer player) { + for (Data data : Data.values()) { + if (data.onQuit != null) { + Datapoint produced = data.onQuit.apply(player); + Datapoint target = get(data); + target.setFrom(produced); + } + } + } + + public static ArcadeDataHandler initUserWithDefaultData(UUID uuid) { + ArcadeDataHandler handler = new ArcadeDataHandler(); + handler.uuid = uuid; + for (Data data : Data.values()) { + try { + handler.datapoints.put( + data.getKey(), + data.getDefaultDatapoint().deepClone().setUser(handler).setData(data) + ); + } catch (Exception e) { + Logger.error("Issue with datapoint " + data.getKey() + " for user " + uuid + " - fix"); + e.printStackTrace(); + } + } + return handler; + } + + public enum Data { + COINS("arcade_coins", DatapointLong.class, + new DatapointLeaderboardLong("arcade_coins", 0L, "arcade:coins")), + TOTAL_WINS("total_wins", DatapointLong.class, + new DatapointLeaderboardLong("arcade_total_wins", 0L, "arcade:wins")), + ZOMBIES_STATS("skywars_mode_stats", DatapointZombiesModeStats.class, + new DatapointZombiesModeStats("arcade_zombies_stats")), + ; + + @Getter + private final String key; + @Getter private final Class> type; + @Getter private final Datapoint defaultDatapoint; + public final BiConsumer> onChange; + public final BiConsumer> onLoad; + public final Function> onQuit; + + Data(String key, + Class> type, + Datapoint defaultDatapoint, + BiConsumer> onChange, + BiConsumer> onLoad, + Function> onQuit) { + this.key = key; this.type = type; this.defaultDatapoint = defaultDatapoint; + this.onChange = onChange; this.onLoad = onLoad; this.onQuit = onQuit; + } + + Data(String key, Class> type, Datapoint defaultDatapoint, + BiConsumer> onChange, BiConsumer> onLoad) { + this(key, type, defaultDatapoint, onChange, onLoad, null); + } + + Data(String key, Class> type, Datapoint defaultDatapoint, + BiConsumer> onChange) { + this(key, type, defaultDatapoint, onChange, null, null); + } + + Data(String key, Class> type, Datapoint defaultDatapoint) { + this(key, type, defaultDatapoint, null, null, null); + } + + public static Data fromKey(String key) { + for (Data d : values()) if (d.getKey().equals(key)) return d; + return null; + } + } +} diff --git a/type.lobby/src/main/java/net/swofty/type/lobby/game/GameType.java b/type.lobby/src/main/java/net/swofty/type/lobby/game/GameType.java index cc9a7d267..685882452 100644 --- a/type.lobby/src/main/java/net/swofty/type/lobby/game/GameType.java +++ b/type.lobby/src/main/java/net/swofty/type/lobby/game/GameType.java @@ -56,6 +56,14 @@ public enum GameType { "§7Death, Soul Well, and §cINSANE MODE§7!", "§7Play on your own or in teams." ), + + ARCADE_GAMES("Arcade Games §c§lDISASTERS 1.0 RELEASE!", + Material.SLIME_BALL, + Category.CASUAL_GAMES, + ServerType.ARCADE_LOBBY, + "§7Crazy fun minigames to play with", + "§7friends!", + " §f• Zombies"), ; private final String displayName; @@ -108,6 +116,7 @@ public enum Category { SURVIVAL, TEAM_SURVIVAL, COMPETITIVE, + SHOOTER, CASUAL_GAMES, } } diff --git a/type.zombiesconfigurator/build.gradle.kts b/type.zombiesconfigurator/build.gradle.kts new file mode 100644 index 000000000..10ffac0c4 --- /dev/null +++ b/type.zombiesconfigurator/build.gradle.kts @@ -0,0 +1,35 @@ +plugins { + java +} + +group = "net.swofty" +version = "3.0" + +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 + toolchain { + languageVersion.set(JavaLanguageVersion.of(25)) + } +} + +repositories { + maven("https://jitpack.io") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") + maven("https://repo.viaversion.com") +} + +dependencies { + implementation(project(":type.generic")) + implementation(project(":commons")) + implementation(project(":proxy.api")) + implementation("org.mongodb:bson:4.11.2") + implementation("net.kyori:adventure-text-minimessage:4.25.0") + compileOnly("net.minestom:minestom:2025.12.20c-1.21.11") { + exclude(group = "org.jboss.shrinkwrap.resolver", module = "shrinkwrap-resolver-depchain") + } + implementation("dev.hollowcube:polar:1.15.0") + implementation("it.unimi.dsi:fastutil:8.5.18") + implementation("org.tinylog:tinylog-api:2.7.0") + implementation("org.tinylog:tinylog-impl:2.7.0") +} \ No newline at end of file diff --git a/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/MapConfigurationSession.java b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/MapConfigurationSession.java new file mode 100644 index 000000000..f32867404 --- /dev/null +++ b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/MapConfigurationSession.java @@ -0,0 +1,415 @@ +package net.swofty.type.zombiesconfigurator; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import lombok.Getter; +import lombok.Setter; +import net.minestom.server.coordinate.Pos; +import net.swofty.commons.zombies.ZombiesMap; +import net.swofty.commons.zombies.map.ZombiesMapsConfig; +import org.tinylog.Logger; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.*; + +@Getter +@Setter +public class MapConfigurationSession { + private String mapId; + private String mapName; + private ZombiesMap mapType = ZombiesMap.DEAD_END; + + // Locations + private Pos spawnPosition; + private Pos spectatorPosition; + private Pos practiceZombiePosition; + + // Bounds + private Double boundsMinX, boundsMaxX; + private Double boundsMinY, boundsMaxY; + private Double boundsMinZ, boundsMaxZ; + + // Windows + private Map windows = new HashMap<>(); + + // Doors + private Map doors = new HashMap<>(); + + // Spawners + private Map spawners = new HashMap<>(); + + // Boss Spawns + private Map bossSpawns = new HashMap<>(); + + // Shops + private Map shops = new HashMap<>(); + + // Machines + private Map machines = new HashMap<>(); + + // Weapon Chests + private List weaponChests = new ArrayList<>(); + + // Team Machines + private List teamMachines = new ArrayList<>(); + + // Stats Hologram + private Pos statsHologram; + + public MapConfigurationSession(String mapId, String mapName) { + this.mapId = mapId; + this.mapName = mapName; + } + + @Getter + @Setter + public static class WindowData { + private String name; + private List blockPositions = new ArrayList<>(); + private Pos zombieAreaPos1, zombieAreaPos2; + private Pos playerAreaPos1, playerAreaPos2; + private List connectedSpawners = new ArrayList<>(); + } + + @Getter + @Setter + public static class DoorData { + private String name; + private int goldCost; + private Pos areaPos1, areaPos2; + private List connectedDoors = new ArrayList<>(); + private List connectedSpawners = new ArrayList<>(); + private Integer openOnRound; + private boolean isPowerSwitchDoor; + private String fakeDoorName; + } + + @Getter + @Setter + public static class SpawnerData { + private String name; + private Pos location; + private boolean isDefault; + private String requiredDoor; + private List connectedWindows = new ArrayList<>(); + } + + @Getter + @Setter + public static class BossSpawnData { + private String name; + private Pos location; + private String zombieType; + private boolean isDefault; + } + + @Getter + @Setter + public static class ShopData { + private ZombiesMapsConfig.ShopType type; + private Pos location; + private int goldCost; + private ArmorShopData armorConfig; + private WeaponShopData weaponConfig; + private PerkShopData perkConfig; + } + + @Getter + @Setter + public static class ArmorShopData { + private String armorType; + private String armorPart; + } + + @Getter + @Setter + public static class WeaponShopData { + private String weaponName; + private int weaponGold; + private int ammoGold; + } + + @Getter + @Setter + public static class PerkShopData { + private String perkName; + } + + @Getter + @Setter + public static class MachineData { + private String type; + private Pos location; + private int goldCost; + } + + public void saveToFile() { + ZombiesMapsConfig.MapEntry mapEntry = new ZombiesMapsConfig.MapEntry(); + mapEntry.setId(mapId); + mapEntry.setName(mapName); + mapEntry.setMapType(mapType); + + ZombiesMapsConfig.MapEntry.MapConfiguration config = new ZombiesMapsConfig.MapEntry.MapConfiguration(); + + // Set locations + if (spawnPosition != null || spectatorPosition != null || practiceZombiePosition != null) { + ZombiesMapsConfig.MapEntry.MapConfiguration.MapLocations locations = + new ZombiesMapsConfig.MapEntry.MapConfiguration.MapLocations(); + + if (spawnPosition != null) { + locations.setSpawn(new ZombiesMapsConfig.PitchYawPosition( + spawnPosition.x(), spawnPosition.y(), spawnPosition.z(), + spawnPosition.pitch(), spawnPosition.yaw() + )); + } + if (spectatorPosition != null) { + locations.setSpectator(new ZombiesMapsConfig.PitchYawPosition( + spectatorPosition.x(), spectatorPosition.y(), spectatorPosition.z(), + spectatorPosition.pitch(), spectatorPosition.yaw() + )); + } + if (practiceZombiePosition != null) { + locations.setPracticeZombie(new ZombiesMapsConfig.Position( + practiceZombiePosition.x(), practiceZombiePosition.y(), practiceZombiePosition.z() + )); + } + config.setLocations(locations); + } + + // Set bounds + if (boundsMinX != null && boundsMaxX != null && boundsMinY != null && + boundsMaxY != null && boundsMinZ != null && boundsMaxZ != null) { + ZombiesMapsConfig.MapEntry.MapConfiguration.MapBounds bounds = + new ZombiesMapsConfig.MapEntry.MapConfiguration.MapBounds(); + bounds.setX(new ZombiesMapsConfig.MinMax(boundsMinX, boundsMaxX)); + bounds.setY(new ZombiesMapsConfig.MinMax(boundsMinY, boundsMaxY)); + bounds.setZ(new ZombiesMapsConfig.MinMax(boundsMinZ, boundsMaxZ)); + config.setBounds(bounds); + } + + // Set windows + if (!windows.isEmpty()) { + Map windowMap = new HashMap<>(); + for (Map.Entry entry : windows.entrySet()) { + WindowData wd = entry.getValue(); + ZombiesMapsConfig.MapEntry.MapConfiguration.Window window = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Window(); + window.setName(wd.getName()); + + List blockPos = new ArrayList<>(); + for (Pos p : wd.getBlockPositions()) { + blockPos.add(new ZombiesMapsConfig.Position(p.x(), p.y(), p.z())); + } + window.setBlockPositions(blockPos); + + if (wd.getZombieAreaPos1() != null && wd.getZombieAreaPos2() != null) { + ZombiesMapsConfig.MapEntry.MapConfiguration.Window.Area zombieArea = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Window.Area(); + zombieArea.setPos1(new ZombiesMapsConfig.Position( + wd.getZombieAreaPos1().x(), wd.getZombieAreaPos1().y(), wd.getZombieAreaPos1().z() + )); + zombieArea.setPos2(new ZombiesMapsConfig.Position( + wd.getZombieAreaPos2().x(), wd.getZombieAreaPos2().y(), wd.getZombieAreaPos2().z() + )); + window.setZombieArea(zombieArea); + } + + if (wd.getPlayerAreaPos1() != null && wd.getPlayerAreaPos2() != null) { + ZombiesMapsConfig.MapEntry.MapConfiguration.Window.Area playerArea = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Window.Area(); + playerArea.setPos1(new ZombiesMapsConfig.Position( + wd.getPlayerAreaPos1().x(), wd.getPlayerAreaPos1().y(), wd.getPlayerAreaPos1().z() + )); + playerArea.setPos2(new ZombiesMapsConfig.Position( + wd.getPlayerAreaPos2().x(), wd.getPlayerAreaPos2().y(), wd.getPlayerAreaPos2().z() + )); + window.setPlayerArea(playerArea); + } + + window.setConnectedSpawners(wd.getConnectedSpawners()); + windowMap.put(entry.getKey(), window); + } + config.setWindows(windowMap); + } + + // Set doors + if (!doors.isEmpty()) { + Map doorMap = new HashMap<>(); + for (Map.Entry entry : doors.entrySet()) { + DoorData dd = entry.getValue(); + ZombiesMapsConfig.MapEntry.MapConfiguration.Door door = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Door(); + door.setName(dd.getName()); + door.setGoldCost(dd.getGoldCost()); + + if (dd.getAreaPos1() != null && dd.getAreaPos2() != null) { + ZombiesMapsConfig.MapEntry.MapConfiguration.Door.Area area = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Door.Area(); + area.setPos1(new ZombiesMapsConfig.Position( + dd.getAreaPos1().x(), dd.getAreaPos1().y(), dd.getAreaPos1().z() + )); + area.setPos2(new ZombiesMapsConfig.Position( + dd.getAreaPos2().x(), dd.getAreaPos2().y(), dd.getAreaPos2().z() + )); + door.setArea(area); + } + + door.setConnectedDoors(dd.getConnectedDoors()); + door.setConnectedSpawners(dd.getConnectedSpawners()); + door.setOpenOnRound(dd.getOpenOnRound()); + door.setPowerSwitchDoor(dd.isPowerSwitchDoor()); + door.setFakeDoorName(dd.getFakeDoorName()); + doorMap.put(entry.getKey(), door); + } + config.setDoors(doorMap); + } + + // Set spawners + if (!spawners.isEmpty()) { + Map spawnerMap = new HashMap<>(); + for (Map.Entry entry : spawners.entrySet()) { + SpawnerData sd = entry.getValue(); + ZombiesMapsConfig.MapEntry.MapConfiguration.Spawner spawner = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Spawner(); + spawner.setName(sd.getName()); + if (sd.getLocation() != null) { + spawner.setLocation(new ZombiesMapsConfig.Position( + sd.getLocation().x(), sd.getLocation().y(), sd.getLocation().z() + )); + } + spawner.setDefault(sd.isDefault()); + spawner.setRequiredDoor(sd.getRequiredDoor()); + spawner.setConnectedWindows(sd.getConnectedWindows()); + spawnerMap.put(entry.getKey(), spawner); + } + config.setSpawners(spawnerMap); + } + + // Set boss spawns + if (!bossSpawns.isEmpty()) { + Map bossSpawnMap = new HashMap<>(); + for (Map.Entry entry : bossSpawns.entrySet()) { + BossSpawnData bsd = entry.getValue(); + ZombiesMapsConfig.MapEntry.MapConfiguration.BossSpawn bossSpawn = + new ZombiesMapsConfig.MapEntry.MapConfiguration.BossSpawn(); + bossSpawn.setName(bsd.getName()); + if (bsd.getLocation() != null) { + bossSpawn.setLocation(new ZombiesMapsConfig.Position( + bsd.getLocation().x(), bsd.getLocation().y(), bsd.getLocation().z() + )); + } + bossSpawn.setZombieType(bsd.getZombieType()); + bossSpawn.setDefault(bsd.isDefault()); + bossSpawnMap.put(entry.getKey(), bossSpawn); + } + config.setBossSpawns(bossSpawnMap); + } + + // Set shops + if (!shops.isEmpty()) { + Map shopMap = new HashMap<>(); + for (Map.Entry entry : shops.entrySet()) { + ShopData sd = entry.getValue(); + ZombiesMapsConfig.MapEntry.MapConfiguration.Shop shop = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Shop(); + shop.setType(sd.getType()); + if (sd.getLocation() != null) { + shop.setLocation(new ZombiesMapsConfig.Position( + sd.getLocation().x(), sd.getLocation().y(), sd.getLocation().z() + )); + } + shop.setGoldCost(sd.getGoldCost()); + + if (sd.getArmorConfig() != null) { + ZombiesMapsConfig.MapEntry.MapConfiguration.Shop.ArmorShopConfig armorConfig = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Shop.ArmorShopConfig(); + armorConfig.setArmorType(ZombiesMapsConfig.MapEntry.MapConfiguration.Shop.ArmorShopConfig.ArmorType + .valueOf(sd.getArmorConfig().getArmorType())); + armorConfig.setArmorPart(ZombiesMapsConfig.MapEntry.MapConfiguration.Shop.ArmorShopConfig.ArmorPart + .valueOf(sd.getArmorConfig().getArmorPart())); + shop.setArmorConfig(armorConfig); + } + + if (sd.getWeaponConfig() != null) { + ZombiesMapsConfig.MapEntry.MapConfiguration.Shop.WeaponShopConfig weaponConfig = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Shop.WeaponShopConfig(); + weaponConfig.setWeaponName(sd.getWeaponConfig().getWeaponName()); + weaponConfig.setWeaponGold(sd.getWeaponConfig().getWeaponGold()); + weaponConfig.setAmmoGold(sd.getWeaponConfig().getAmmoGold()); + shop.setWeaponConfig(weaponConfig); + } + + if (sd.getPerkConfig() != null) { + ZombiesMapsConfig.MapEntry.MapConfiguration.Shop.PerkShopConfig perkConfig = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Shop.PerkShopConfig(); + perkConfig.setPerkName(sd.getPerkConfig().getPerkName()); + shop.setPerkConfig(perkConfig); + } + + shopMap.put(entry.getKey(), shop); + } + config.setShops(shopMap); + } + + // Set machines + if (!machines.isEmpty()) { + Map machineMap = new HashMap<>(); + for (Map.Entry entry : machines.entrySet()) { + MachineData md = entry.getValue(); + ZombiesMapsConfig.MapEntry.MapConfiguration.Machine machine = + new ZombiesMapsConfig.MapEntry.MapConfiguration.Machine(); + machine.setType(ZombiesMapsConfig.MapEntry.MapConfiguration.Machine.MachineType + .valueOf(md.getType())); + if (md.getLocation() != null) { + machine.setLocation(new ZombiesMapsConfig.Position( + md.getLocation().x(), md.getLocation().y(), md.getLocation().z() + )); + } + machine.setGoldCost(md.getGoldCost()); + machineMap.put(entry.getKey(), machine); + } + config.setMachines(machineMap); + } + + // Set weapon chests + if (!weaponChests.isEmpty()) { + List chestList = new ArrayList<>(); + for (Pos p : weaponChests) { + chestList.add(new ZombiesMapsConfig.Position(p.x(), p.y(), p.z())); + } + config.setWeaponChests(chestList); + } + + // Set team machines + if (!teamMachines.isEmpty()) { + List teamMachineList = new ArrayList<>(); + for (Pos p : teamMachines) { + teamMachineList.add(new ZombiesMapsConfig.Position(p.x(), p.y(), p.z())); + } + config.setTeamMachines(teamMachineList); + } + + // Set stats hologram + if (statsHologram != null) { + config.setStatsHologram(new ZombiesMapsConfig.Position( + statsHologram.x(), statsHologram.y(), statsHologram.z() + )); + } + + mapEntry.setConfiguration(config); + + // Save to file + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + File outputFile = new File("configuration/zombies/" + mapId + ".json"); + outputFile.getParentFile().mkdirs(); + + try (FileWriter writer = new FileWriter(outputFile)) { + gson.toJson(mapEntry, writer); + Logger.info("Saved Zombies map configuration to " + outputFile.getAbsolutePath()); + } catch (IOException e) { + Logger.error(e, "Failed to save Zombies map configuration"); + } + } +} diff --git a/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/TypeZombiesConfiguratorLoader.java b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/TypeZombiesConfiguratorLoader.java new file mode 100644 index 000000000..eb2ef4dd8 --- /dev/null +++ b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/TypeZombiesConfiguratorLoader.java @@ -0,0 +1,142 @@ +package net.swofty.type.zombiesconfigurator; + +import lombok.Getter; +import lombok.Setter; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.entity.GameMode; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.instance.InstanceManager; +import net.minestom.server.network.player.GameProfile; +import net.minestom.server.network.player.PlayerConnection; +import net.swofty.commons.CustomWorlds; +import net.swofty.commons.ServerType; +import net.swofty.commons.ServiceType; +import net.swofty.proxyapi.redis.ProxyToClient; +import net.swofty.proxyapi.redis.ServiceToClient; +import net.swofty.type.generic.HypixelGenericLoader; +import net.swofty.type.generic.HypixelTypeLoader; +import net.swofty.type.generic.command.HypixelCommand; +import net.swofty.type.generic.entity.npc.HypixelNPC; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.redis.RedisOriginServer; +import net.swofty.type.generic.tab.EmptyTabModule; +import net.swofty.type.generic.tab.TablistManager; +import net.swofty.type.generic.tab.TablistModule; +import net.swofty.type.generic.user.HypixelPlayer; +import org.jetbrains.annotations.Nullable; +import org.tinylog.Logger; + +import java.util.List; +import java.util.UUID; + +public class TypeZombiesConfiguratorLoader implements HypixelTypeLoader { + private static InstanceContainer mainInstance; + + @Setter + @Getter + private static MapConfigurationSession currentSession; + + public static InstanceContainer getInstance() { + return mainInstance; + } + + @Override + public ServerType getType() { + return ServerType.ZOMBIES_CONFIGURATOR; + } + + @Override + public void onInitialize(MinecraftServer server) { + InstanceManager instanceManager = MinecraftServer.getInstanceManager(); + mainInstance = instanceManager.createInstanceContainer(); + + MinecraftServer.getConnectionManager().setPlayerProvider((PlayerConnection playerConnection, GameProfile gameProfile) -> { + HypixelPlayer player = new HypixelPlayer(playerConnection, gameProfile); + + UUID uuid = gameProfile.uuid(); + String username = gameProfile.name(); + + if (RedisOriginServer.origin.containsKey(uuid)) { + player.setOriginServer(RedisOriginServer.origin.get(uuid)); + RedisOriginServer.origin.remove(uuid); + } + + // Set player to creative mode for configuration + player.setGameMode(GameMode.CREATIVE); + + Logger.info("Zombies Configurator: " + username + " joined"); + + return player; + }); + } + + @Override + public void afterInitialize(MinecraftServer server) { + HypixelGenericLoader.loopThroughPackage("net.swofty.type.zombiesconfigurator.commands", HypixelCommand.class).forEach(command -> { + try { + MinecraftServer.getCommandManager().register(command.getCommand()); + } catch (Exception e) { + Logger.error(e, "Failed to register command " + command.getCommand().getName()); + } + }); + + Logger.info("Zombies Configurator loaded. Use /zombies to start."); + } + + @Override + public List getRequiredServices() { + return List.of(); + } + + @Override + public TablistManager getTablistManager() { + return new TablistManager() { + @Override + public List getModules() { + return List.of(new EmptyTabModule()); + } + }; + } + + @Override + public LoaderValues getLoaderValues() { + return new LoaderValues( + (_) -> new Pos(0, 100, 0, 0, 0), + false + ); + } + + @Override + public List getTraditionalEvents() { + return HypixelGenericLoader.loopThroughPackage( + "net.swofty.type.zombiesconfigurator.events", + HypixelEventClass.class + ).toList(); + } + + @Override + public List getCustomEvents() { + return List.of(); + } + + @Override + public List getNPCs() { + return List.of(); + } + + @Override + public List getServiceRedisListeners() { + return List.of(); + } + + @Override + public List getProxyRedisListeners() { + return List.of(); + } + + @Override + public @Nullable CustomWorlds getMainInstance() { + return null; + } +} diff --git a/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/commands/ChooseMapCommand.java b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/commands/ChooseMapCommand.java new file mode 100644 index 000000000..739408020 --- /dev/null +++ b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/commands/ChooseMapCommand.java @@ -0,0 +1,225 @@ +package net.swofty.type.zombiesconfigurator.commands; + +import net.hollowcube.polar.AnvilPolar; +import net.hollowcube.polar.PolarLoader; +import net.hollowcube.polar.PolarWorld; +import net.hollowcube.polar.PolarWriter; +import net.kyori.adventure.text.Component; +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.command.builder.suggestion.SuggestionEntry; +import net.minestom.server.entity.Player; +import net.minestom.server.instance.InstanceContainer; +import net.swofty.type.generic.command.CommandParameters; +import net.swofty.type.generic.command.HypixelCommand; +import net.swofty.type.generic.user.categories.Rank; +import net.swofty.type.zombiesconfigurator.MapConfigurationSession; +import net.swofty.type.zombiesconfigurator.TypeZombiesConfiguratorLoader; +import org.tinylog.Logger; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +@CommandParameters(aliases = "choose choosemap selectmap select", + description = "Choose a Zombies map to configure", + usage = "/choosemap ", + permission = Rank.STAFF, + allowsConsole = false) +public class ChooseMapCommand extends HypixelCommand { + + private static final Path CONFIG_PATH = Path.of("./configuration/zombies/"); + + @Override + public void registerUsage(MinestomCommand command) { + var mapArg = ArgumentType.String("map"); + mapArg.setSuggestionCallback((sender, context, suggestion) -> { + Set addedIds = new HashSet<>(); + + // Add polar files + File configDir = CONFIG_PATH.toFile(); + if (configDir.exists() && configDir.isDirectory()) { + File[] polarFiles = configDir.listFiles((dir, name) -> name.endsWith(".polar")); + if (polarFiles != null) { + for (File polarFile : polarFiles) { + String mapId = polarFile.getName().replace(".polar", ""); + if (!addedIds.contains(mapId.toLowerCase())) { + suggestion.addEntry(new SuggestionEntry(mapId, Component.text(mapId + " §a(polar)"))); + addedIds.add(mapId.toLowerCase()); + } + } + } + + // Add Anvil world folders (need conversion) + File[] anvilFolders = configDir.listFiles(file -> { + if (!file.isDirectory()) return false; + // Check if it's an Anvil world (has region folder) + File regionDir = new File(file, "region"); + return regionDir.exists() && regionDir.isDirectory(); + }); + if (anvilFolders != null) { + for (File anvilFolder : anvilFolders) { + String mapId = anvilFolder.getName(); + if (!addedIds.contains(mapId.toLowerCase())) { + suggestion.addEntry(new SuggestionEntry(mapId, Component.text(mapId + " §c(anvil - needs conversion)"))); + } + } + } + } + }); + + command.addSyntax((sender, context) -> { + if (!(sender instanceof Player player)) { + sender.sendMessage(Component.text("§cThis command can only be executed by a player.")); + return; + } + String mapId = context.get("map"); + + File polarFile = CONFIG_PATH.resolve(mapId + ".polar").toFile(); + File anvilFolder = CONFIG_PATH.resolve(mapId).toFile(); + File regionDir = new File(anvilFolder, "region"); + + // Check if we need to convert from Anvil + if (!polarFile.exists() && anvilFolder.exists() && regionDir.exists()) { + player.sendMessage(Component.text("§eFound Anvil world folder for: " + mapId)); + player.sendMessage(Component.text("§eConverting to Polar format... This may take a moment.")); + + try { + convertAnvilToPolar(player, mapId, anvilFolder, polarFile); + } catch (Exception e) { + player.sendMessage(Component.text("§cFailed to convert world: " + e.getMessage())); + Logger.error("Failed to convert Anvil to Polar", e); + return; + } + } + + // Now check if polar file exists + if (!polarFile.exists()) { + sender.sendMessage(Component.text("§cNo polar file or Anvil world found for map: " + mapId)); + sender.sendMessage(Component.text("§7Place a .polar file or Anvil world folder in: " + CONFIG_PATH.toAbsolutePath())); + return; + } + + // Load the polar world + InstanceContainer mapInstance = MinecraftServer.getInstanceManager().createInstanceContainer(); + try { + mapInstance.setChunkLoader(new PolarLoader(polarFile.toPath())); + } catch (IOException e) { + sender.sendMessage(Component.text("§cFailed to load map: " + mapId)); + Logger.error("Failed to load polar file", e); + return; + } + + // Create a new configuration session + MapConfigurationSession session = new MapConfigurationSession(mapId, mapId); + TypeZombiesConfiguratorLoader.setCurrentSession(session); + + player.sendMessage(Component.text("§aLoaded map: §f" + mapId)); + player.sendMessage(Component.text("§7Use §b/zombies §7to configure the map.")); + + player.setInstance(mapInstance); + }, mapArg); + } + + private void convertAnvilToPolar(Player player, String mapId, File anvilFolder, File polarFile) throws Exception { + // Find all populated chunks from region files + File regionDir = new File(anvilFolder, "region"); + File[] regionFiles = regionDir.listFiles((dir, name) -> name.endsWith(".mca")); + + if (regionFiles == null || regionFiles.length == 0) { + throw new Exception("No region files found in " + regionDir.getAbsolutePath()); + } + + player.sendMessage(Component.text("§7Found " + regionFiles.length + " region file(s)...")); + + // Collect all populated chunk coordinates + Set populatedChunks = new HashSet<>(); + + for (File regionFile : regionFiles) { + // Parse region coordinates from filename: r.X.Z.mca + String name = regionFile.getName(); + String[] parts = name.replace("r.", "").replace(".mca", "").split("\\."); + if (parts.length != 2) continue; + + int regionX = Integer.parseInt(parts[0]); + int regionZ = Integer.parseInt(parts[1]); + + // Scan the region file to find populated chunks + List chunks = getPopulatedChunksFromRegion(regionFile, regionX, regionZ); + for (int[] chunk : chunks) { + populatedChunks.add(((long) chunk[0] << 32) | (chunk[1] & 0xFFFFFFFFL)); + } + } + + player.sendMessage(Component.text("§7Converting " + populatedChunks.size() + " chunks to Polar format...")); + + // Convert Anvil to Polar using only populated chunks + PolarWorld polarWorld = AnvilPolar.anvilToPolar( + anvilFolder.toPath(), + (x, z) -> populatedChunks.contains(((long) x << 32) | (z & 0xFFFFFFFFL)) + ); + + // Write to file + byte[] polarBytes = PolarWriter.write(polarWorld); + Files.write(polarFile.toPath(), polarBytes); + + player.sendMessage(Component.text("§aPolar file created: " + polarFile.getName())); + + // Delete the original Anvil folder + player.sendMessage(Component.text("§7Deleting original Anvil folder...")); + deleteDirectory(anvilFolder.toPath()); + player.sendMessage(Component.text("§aOriginal Anvil folder deleted.")); + } + + /** + * Scans a region file to find which chunks actually contain data + */ + private List getPopulatedChunksFromRegion(File regionFile, int regionX, int regionZ) { + List chunks = new ArrayList<>(); + + try (RandomAccessFile raf = new RandomAccessFile(regionFile, "r")) { + // The first 4096 bytes contain the chunk offset table + // Each chunk has 4 bytes: 3 bytes offset + 1 byte sector count + for (int localZ = 0; localZ < 32; localZ++) { + for (int localX = 0; localX < 32; localX++) { + int index = (localX + localZ * 32) * 4; + raf.seek(index); + + int offset = raf.readInt(); + // If offset is 0, chunk doesn't exist + if (offset != 0) { + int chunkX = regionX * 32 + localX; + int chunkZ = regionZ * 32 + localZ; + chunks.add(new int[]{chunkX, chunkZ}); + } + } + } + } catch (IOException e) { + Logger.warn("Failed to read region file: " + regionFile.getName(), e); + // Fallback: load all 32x32 chunks in region + for (int localZ = 0; localZ < 32; localZ++) { + for (int localX = 0; localX < 32; localX++) { + int chunkX = regionX * 32 + localX; + int chunkZ = regionZ * 32 + localZ; + chunks.add(new int[]{chunkX, chunkZ}); + } + } + } + + return chunks; + } + + private void deleteDirectory(Path path) throws IOException { + if (Files.isDirectory(path)) { + try (var entries = Files.list(path)) { + for (Path entry : entries.toList()) { + deleteDirectory(entry); + } + } + } + Files.deleteIfExists(path); + } +} diff --git a/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/commands/ZombiesConfigCommand.java b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/commands/ZombiesConfigCommand.java new file mode 100644 index 000000000..9fb80982c --- /dev/null +++ b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/commands/ZombiesConfigCommand.java @@ -0,0 +1,731 @@ +package net.swofty.type.zombiesconfigurator.commands; + +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.command.builder.suggestion.SuggestionEntry; +import net.minestom.server.coordinate.Pos; +import net.swofty.commons.zombies.ZombiesMap; +import net.swofty.commons.zombies.map.ZombiesMapsConfig; +import net.swofty.type.generic.command.CommandParameters; +import net.swofty.type.generic.command.HypixelCommand; +import net.swofty.type.generic.user.HypixelPlayer; +import net.swofty.type.generic.user.categories.Rank; +import net.swofty.type.zombiesconfigurator.MapConfigurationSession; +import net.swofty.type.zombiesconfigurator.TypeZombiesConfiguratorLoader; + +import java.util.Arrays; +import java.util.stream.Collectors; + +@CommandParameters( + aliases = "zombiesconfig", + description = "Configure Zombies maps", + usage = "/zombiesconfig ", + permission = Rank.STAFF, + allowsConsole = false +) +public class ZombiesConfigCommand extends HypixelCommand { + + @Override + public void registerUsage(MinestomCommand command) { + // Default - show usage + command.setDefaultExecutor((sender, context) -> { + sender.sendMessage("§e=== Zombies Map Configuration ==="); + sender.sendMessage("§7/zombies new §f- Start new session"); + sender.sendMessage("§7/zombies setMap §f- Set map type"); + sender.sendMessage("§7/zombies setSpawn §f- Set spawn position"); + sender.sendMessage("§7/zombies setSpectator §f- Set spectator position"); + sender.sendMessage("§7/zombies setPracticeZombie §f- Set practice zombie position"); + sender.sendMessage("§7/zombies setBounds §f- Set map bounds"); + sender.sendMessage("§7/zombies addDefaultBossSpawn §f- Add default boss spawn"); + sender.sendMessage("§7/zombies addBossSpawn §f- Add boss spawn"); + sender.sendMessage("§7/zombies createArea <1/2> §f- Create area positions"); + sender.sendMessage("§7/zombies saveBlockWindow §f- Save window blocks"); + sender.sendMessage("§7/zombies saveZombieWindowArea §f- Save zombie window area"); + sender.sendMessage("§7/zombies savePlayerWindowArea §f- Save player window area"); + sender.sendMessage("§7/zombies saveDoor §f- Save door"); + sender.sendMessage("§7/zombies saveFakeDoor §f- Save fake door"); + sender.sendMessage("§7/zombies toggleDoor §f- Toggle door round"); + sender.sendMessage("§7/zombies togglePowerSwitchDoor §f- Toggle power switch door"); + sender.sendMessage("§7/zombies connectDoors §f- Connect doors"); + sender.sendMessage("§7/zombies addDefaultSpawner §f- Add default spawner"); + sender.sendMessage("§7/zombies addSpawner §f- Add spawner"); + sender.sendMessage("§7/zombies connectSpawner §f- Connect spawner"); + sender.sendMessage("§7/zombies saveArmorPackShop §f- Save armor shop"); + sender.sendMessage("§7/zombies saveWeaponShop §f- Save weapon shop"); + sender.sendMessage("§7/zombies savePerkShop §f- Save perk shop"); + sender.sendMessage("§7/zombies savePowerSwitch §f- Save power switch"); + sender.sendMessage("§7/zombies addWeaponsChest §f- Add weapons chest"); + sender.sendMessage("§7/zombies addTeamMachine §f- Add team machine"); + sender.sendMessage("§7/zombies saveUltimateMachine §f- Save ultimate machine"); + sender.sendMessage("§7/zombies saveStatsHologram §f- Save stats hologram"); + sender.sendMessage("§7/zombies save §f- Save configuration to file"); + sender.sendMessage("§7/zombies status §f- Show current status"); + }); + + // /zombies new + var newLit = ArgumentType.Literal("new"); + var idArg = ArgumentType.String("id"); + var nameArg = ArgumentType.String("name"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + String id = context.get(idArg); + String name = context.get(nameArg); + MapConfigurationSession session = new MapConfigurationSession(id, name); + TypeZombiesConfiguratorLoader.setCurrentSession(session); + sender.sendMessage("§aStarted new configuration session for map: " + name + " (id: " + id + ")"); + }, newLit, idArg, nameArg); + + // /zombies setMap + var setMapLit = ArgumentType.Literal("setMap"); + var mapTypeArg = ArgumentType.String("mapType"); + mapTypeArg.setSuggestionCallback((sender, context, suggestion) -> { + for (ZombiesMap map : ZombiesMap.values()) { + suggestion.addEntry(new SuggestionEntry(map.name())); + } + }); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session! Use /zombies new first."); + return; + } + String mapTypeName = context.get(mapTypeArg); + try { + ZombiesMap mapType = ZombiesMap.valueOf(mapTypeName.toUpperCase()); + session.setMapType(mapType); + sender.sendMessage("§aSet map type to: " + mapType.name()); + } catch (IllegalArgumentException e) { + sender.sendMessage("§cInvalid map type! Available: " + + Arrays.stream(ZombiesMap.values()).map(Enum::name).collect(Collectors.joining(", "))); + } + }, setMapLit, mapTypeArg); + + // /zombies setSpawn + var setSpawnLit = ArgumentType.Literal("setSpawn"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + session.setSpawnPosition(player.getPosition()); + sender.sendMessage("§aSet spawn position to " + formatPos(player.getPosition())); + }, setSpawnLit); + + // /zombies setSpectator + var setSpectatorLit = ArgumentType.Literal("setSpectator"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + session.setSpectatorPosition(player.getPosition()); + sender.sendMessage("§aSet spectator position to " + formatPos(player.getPosition())); + }, setSpectatorLit); + + // /zombies setPracticeZombie + var setPracticeZombieLit = ArgumentType.Literal("setPracticeZombie"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + session.setPracticeZombiePosition(player.getPosition()); + sender.sendMessage("§aSet practice zombie position to " + formatPos(player.getPosition())); + }, setPracticeZombieLit); + + // /zombies setBounds + var setBoundsLit = ArgumentType.Literal("setBounds"); + var minXArg = ArgumentType.Double("minX"); + var minYArg = ArgumentType.Double("minY"); + var minZArg = ArgumentType.Double("minZ"); + var maxXArg = ArgumentType.Double("maxX"); + var maxYArg = ArgumentType.Double("maxY"); + var maxZArg = ArgumentType.Double("maxZ"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + session.setBoundsMinX(context.get(minXArg)); + session.setBoundsMinY(context.get(minYArg)); + session.setBoundsMinZ(context.get(minZArg)); + session.setBoundsMaxX(context.get(maxXArg)); + session.setBoundsMaxY(context.get(maxYArg)); + session.setBoundsMaxZ(context.get(maxZArg)); + sender.sendMessage("§aSet bounds successfully!"); + }, setBoundsLit, minXArg, minYArg, minZArg, maxXArg, maxYArg, maxZArg); + + // /zombies addDefaultBossSpawn + var addDefaultBossSpawnLit = ArgumentType.Literal("addDefaultBossSpawn"); + var bossNameArg = ArgumentType.String("bossName"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String bossName = context.get(bossNameArg); + MapConfigurationSession.BossSpawnData bossSpawn = new MapConfigurationSession.BossSpawnData(); + bossSpawn.setName(bossName); + bossSpawn.setLocation(player.getPosition()); + bossSpawn.setDefault(true); + session.getBossSpawns().put(bossName, bossSpawn); + sender.sendMessage("§aAdded default boss spawn: " + bossName); + }, addDefaultBossSpawnLit, bossNameArg); + + // /zombies addBossSpawn + var addBossSpawnLit = ArgumentType.Literal("addBossSpawn"); + var zombieTypeArg = ArgumentType.String("zombieType"); + var bossSpawnNameArg = ArgumentType.String("name"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String zombieType = context.get(zombieTypeArg); + String name = context.get(bossSpawnNameArg); + MapConfigurationSession.BossSpawnData bossSpawn = new MapConfigurationSession.BossSpawnData(); + bossSpawn.setName(name); + bossSpawn.setLocation(player.getPosition()); + bossSpawn.setZombieType(zombieType); + bossSpawn.setDefault(false); + session.getBossSpawns().put(name, bossSpawn); + sender.sendMessage("§aAdded boss spawn: " + name + " (" + zombieType + ")"); + }, addBossSpawnLit, zombieTypeArg, bossSpawnNameArg); + + registerWindowCommands(command); + registerDoorCommands(command); + registerSpawnerCommands(command); + registerShopCommands(command); + registerMachineCommands(command); + registerMiscCommands(command); + } + + private void registerWindowCommands(MinestomCommand command) { + // /zombies saveBlockWindow + var saveBlockWindowLit = ArgumentType.Literal("saveBlockWindow"); + var windowNameArg = ArgumentType.String("windowName"); + var nameArg = ArgumentType.String("name"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String windowName = context.get(windowNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.WindowData window = session.getWindows() + .computeIfAbsent(name, k -> new MapConfigurationSession.WindowData()); + window.setName(windowName); + window.getBlockPositions().add(player.getPosition()); + sender.sendMessage("§aAdded block position to window: " + name + " (" + window.getBlockPositions().size() + " blocks)"); + }, saveBlockWindowLit, windowNameArg, nameArg); + + // /zombies saveZombieWindowArea + var saveZombieWindowAreaLit = ArgumentType.Literal("saveZombieWindowArea"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String windowName = context.get(windowNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.WindowData window = session.getWindows() + .computeIfAbsent(name, k -> new MapConfigurationSession.WindowData()); + window.setName(windowName); + + if (window.getZombieAreaPos1() == null) { + window.setZombieAreaPos1(player.getPosition()); + sender.sendMessage("§aSet zombie window area position 1 for: " + name); + } else { + window.setZombieAreaPos2(player.getPosition()); + sender.sendMessage("§aSet zombie window area position 2 for: " + name); + } + }, saveZombieWindowAreaLit, windowNameArg, nameArg); + + // /zombies savePlayerWindowArea + var savePlayerWindowAreaLit = ArgumentType.Literal("savePlayerWindowArea"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String windowName = context.get(windowNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.WindowData window = session.getWindows() + .computeIfAbsent(name, k -> new MapConfigurationSession.WindowData()); + window.setName(windowName); + + if (window.getPlayerAreaPos1() == null) { + window.setPlayerAreaPos1(player.getPosition()); + sender.sendMessage("§aSet player window area position 1 for: " + name); + } else { + window.setPlayerAreaPos2(player.getPosition()); + sender.sendMessage("§aSet player window area position 2 for: " + name); + } + }, savePlayerWindowAreaLit, windowNameArg, nameArg); + } + + private void registerDoorCommands(MinestomCommand command) { + // /zombies saveDoor + var saveDoorLit = ArgumentType.Literal("saveDoor"); + var goldArg = ArgumentType.Integer("gold"); + var doorNameArg = ArgumentType.String("doorName"); + var nameArg = ArgumentType.String("name"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + int gold = context.get(goldArg); + String doorName = context.get(doorNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.DoorData door = session.getDoors() + .computeIfAbsent(name, k -> new MapConfigurationSession.DoorData()); + door.setName(doorName); + door.setGoldCost(gold); + + if (door.getAreaPos1() == null) { + door.setAreaPos1(player.getPosition()); + sender.sendMessage("§aSet door area position 1 for: " + name); + } else { + door.setAreaPos2(player.getPosition()); + sender.sendMessage("§aSet door area position 2 for: " + name + " (Complete)"); + } + }, saveDoorLit, goldArg, doorNameArg, nameArg); + + // /zombies saveFakeDoor + var saveFakeDoorLit = ArgumentType.Literal("saveFakeDoor"); + var fakeDoorNameArg = ArgumentType.String("fakeDoorName"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + int gold = context.get(goldArg); + String doorName = context.get(doorNameArg); + String fakeDoorName = context.get(fakeDoorNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.DoorData door = session.getDoors() + .computeIfAbsent(name, k -> new MapConfigurationSession.DoorData()); + door.setName(doorName); + door.setGoldCost(gold); + door.setFakeDoorName(fakeDoorName); + + if (door.getAreaPos1() == null) { + door.setAreaPos1(player.getPosition()); + sender.sendMessage("§aSet fake door area position 1 for: " + name); + } else { + door.setAreaPos2(player.getPosition()); + sender.sendMessage("§aSet fake door area position 2 for: " + name + " (Complete)"); + } + }, saveFakeDoorLit, goldArg, doorNameArg, fakeDoorNameArg, nameArg); + + // /zombies toggleDoor + var toggleDoorLit = ArgumentType.Literal("toggleDoor"); + var roundIdArg = ArgumentType.Integer("roundId"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String doorName = context.get(doorNameArg); + int roundId = context.get(roundIdArg); + String name = context.get(nameArg); + + MapConfigurationSession.DoorData door = session.getDoors().get(name); + if (door == null) { + sender.sendMessage("§cDoor not found: " + name); + return; + } + door.setOpenOnRound(roundId); + sender.sendMessage("§aSet door " + name + " to open on round: " + roundId); + }, toggleDoorLit, doorNameArg, roundIdArg, nameArg); + + // /zombies togglePowerSwitchDoor + var togglePowerSwitchDoorLit = ArgumentType.Literal("togglePowerSwitchDoor"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String doorName = context.get(doorNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.DoorData door = session.getDoors().get(name); + if (door == null) { + sender.sendMessage("§cDoor not found: " + name); + return; + } + door.setPowerSwitchDoor(!door.isPowerSwitchDoor()); + sender.sendMessage("§aToggled power switch door for " + name + ": " + door.isPowerSwitchDoor()); + }, togglePowerSwitchDoorLit, doorNameArg, nameArg); + + // /zombies connectDoors + var connectDoorsLit = ArgumentType.Literal("connectDoors"); + var connectedDoorsArg = ArgumentType.String("connectedDoors"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String doorName = context.get(doorNameArg); + String connectedDoorsStr = context.get(connectedDoorsArg); + String name = context.get(nameArg); + + MapConfigurationSession.DoorData door = session.getDoors().get(name); + if (door == null) { + sender.sendMessage("§cDoor not found: " + name); + return; + } + door.setConnectedDoors(Arrays.asList(connectedDoorsStr.split(","))); + sender.sendMessage("§aConnected doors for " + name + ": " + connectedDoorsStr); + }, connectDoorsLit, doorNameArg, connectedDoorsArg, nameArg); + } + + private void registerSpawnerCommands(MinestomCommand command) { + // /zombies addDefaultSpawner + var addDefaultSpawnerLit = ArgumentType.Literal("addDefaultSpawner"); + var spawnerNameArg = ArgumentType.String("spawnerName"); + var nameArg = ArgumentType.String("name"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String spawnerName = context.get(spawnerNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.SpawnerData spawner = new MapConfigurationSession.SpawnerData(); + spawner.setName(spawnerName); + spawner.setLocation(player.getPosition()); + spawner.setDefault(true); + session.getSpawners().put(name, spawner); + sender.sendMessage("§aAdded default spawner: " + name); + }, addDefaultSpawnerLit, spawnerNameArg, nameArg); + + // /zombies addSpawner + var addSpawnerLit = ArgumentType.Literal("addSpawner"); + var doorNameArg = ArgumentType.String("doorName"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String spawnerName = context.get(spawnerNameArg); + String doorName = context.get(doorNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.SpawnerData spawner = new MapConfigurationSession.SpawnerData(); + spawner.setName(spawnerName); + spawner.setLocation(player.getPosition()); + spawner.setRequiredDoor(doorName); + spawner.setDefault(false); + session.getSpawners().put(name, spawner); + sender.sendMessage("§aAdded spawner: " + name + " (requires door: " + doorName + ")"); + }, addSpawnerLit, spawnerNameArg, doorNameArg, nameArg); + + // /zombies connectSpawner + var connectSpawnerLit = ArgumentType.Literal("connectSpawner"); + var windowNameArg = ArgumentType.String("windowName"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String spawnerName = context.get(spawnerNameArg); + String windowName = context.get(windowNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.SpawnerData spawner = session.getSpawners().get(name); + if (spawner == null) { + sender.sendMessage("§cSpawner not found: " + name); + return; + } + spawner.getConnectedWindows().add(windowName); + sender.sendMessage("§aConnected spawner " + name + " to window: " + windowName); + }, connectSpawnerLit, spawnerNameArg, windowNameArg, nameArg); + } + + private void registerShopCommands(MinestomCommand command) { + // /zombies saveArmorPackShop + var saveArmorPackShopLit = ArgumentType.Literal("saveArmorPackShop"); + var goldArg = ArgumentType.Integer("gold"); + var armorTypeArg = ArgumentType.String("armorType"); + var armorPartArg = ArgumentType.String("armorPart"); + var nameArg = ArgumentType.String("name"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + int gold = context.get(goldArg); + String armorType = context.get(armorTypeArg).toUpperCase(); + String armorPart = context.get(armorPartArg).toUpperCase(); + String name = context.get(nameArg); + + MapConfigurationSession.ShopData shop = new MapConfigurationSession.ShopData(); + shop.setType(ZombiesMapsConfig.ShopType.ARMOR); + shop.setLocation(player.getPosition()); + shop.setGoldCost(gold); + + MapConfigurationSession.ArmorShopData armorConfig = new MapConfigurationSession.ArmorShopData(); + armorConfig.setArmorType(armorType); + armorConfig.setArmorPart(armorPart); + shop.setArmorConfig(armorConfig); + + session.getShops().put(name, shop); + sender.sendMessage("§aAdded armor shop: " + name + " (" + armorType + " " + armorPart + ")"); + }, saveArmorPackShopLit, goldArg, armorTypeArg, armorPartArg, nameArg); + + // /zombies saveWeaponShop + var saveWeaponShopLit = ArgumentType.Literal("saveWeaponShop"); + var weaponGoldArg = ArgumentType.Integer("weaponGold"); + var ammoGoldArg = ArgumentType.Integer("ammoGold"); + var weaponNameArg = ArgumentType.String("weaponName"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + int weaponGold = context.get(weaponGoldArg); + int ammoGold = context.get(ammoGoldArg); + String weaponName = context.get(weaponNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.ShopData shop = new MapConfigurationSession.ShopData(); + shop.setType(ZombiesMapsConfig.ShopType.WEAPON); + shop.setLocation(player.getPosition()); + shop.setGoldCost(weaponGold); + + MapConfigurationSession.WeaponShopData weaponConfig = new MapConfigurationSession.WeaponShopData(); + weaponConfig.setWeaponName(weaponName); + weaponConfig.setWeaponGold(weaponGold); + weaponConfig.setAmmoGold(ammoGold); + shop.setWeaponConfig(weaponConfig); + + session.getShops().put(name, shop); + sender.sendMessage("§aAdded weapon shop: " + name + " (" + weaponName + ")"); + }, saveWeaponShopLit, weaponGoldArg, ammoGoldArg, weaponNameArg, nameArg); + + // /zombies savePerkShop + var savePerkShopLit = ArgumentType.Literal("savePerkShop"); + var perkGoldArg = ArgumentType.Integer("perkGold"); + var perkNameArg = ArgumentType.String("perkName"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + int perkGold = context.get(perkGoldArg); + String perkName = context.get(perkNameArg); + String name = context.get(nameArg); + + MapConfigurationSession.ShopData shop = new MapConfigurationSession.ShopData(); + shop.setType(ZombiesMapsConfig.ShopType.PERK); + shop.setLocation(player.getPosition()); + shop.setGoldCost(perkGold); + + MapConfigurationSession.PerkShopData perkConfig = new MapConfigurationSession.PerkShopData(); + perkConfig.setPerkName(perkName); + shop.setPerkConfig(perkConfig); + + session.getShops().put(name, shop); + sender.sendMessage("§aAdded perk shop: " + name + " (" + perkName + ")"); + }, savePerkShopLit, perkGoldArg, perkNameArg, nameArg); + } + + private void registerMachineCommands(MinestomCommand command) { + // /zombies savePowerSwitch + var savePowerSwitchLit = ArgumentType.Literal("savePowerSwitch"); + var powerSwitchGoldArg = ArgumentType.Integer("powerSwitchGold"); + var nameArg = ArgumentType.String("name"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + int gold = context.get(powerSwitchGoldArg); + String name = context.get(nameArg); + + MapConfigurationSession.MachineData machine = new MapConfigurationSession.MachineData(); + machine.setType("POWER_SWITCH"); + machine.setLocation(player.getPosition()); + machine.setGoldCost(gold); + + session.getMachines().put(name, machine); + sender.sendMessage("§aAdded power switch: " + name); + }, savePowerSwitchLit, powerSwitchGoldArg, nameArg); + + // /zombies saveUltimateMachine + var saveUltimateMachineLit = ArgumentType.Literal("saveUltimateMachine"); + var goldArg = ArgumentType.Integer("gold"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + int gold = context.get(goldArg); + String name = context.get(nameArg); + + MapConfigurationSession.MachineData machine = new MapConfigurationSession.MachineData(); + machine.setType("ULTIMATE_MACHINE"); + machine.setLocation(player.getPosition()); + machine.setGoldCost(gold); + + session.getMachines().put(name, machine); + sender.sendMessage("§aAdded ultimate machine: " + name); + }, saveUltimateMachineLit, goldArg, nameArg); + + // /zombies addWeaponsChest + var addWeaponsChestLit = ArgumentType.Literal("addWeaponsChest"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String name = context.get(nameArg); + session.getWeaponChests().add(player.getPosition()); + sender.sendMessage("§aAdded weapon chest at current position (" + session.getWeaponChests().size() + ")"); + }, addWeaponsChestLit, nameArg); + + // /zombies addTeamMachine + var addTeamMachineLit = ArgumentType.Literal("addTeamMachine"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + String name = context.get(nameArg); + session.getTeamMachines().add(player.getPosition()); + sender.sendMessage("§aAdded team machine at current position (" + session.getTeamMachines().size() + ")"); + }, addTeamMachineLit, nameArg); + } + + private void registerMiscCommands(MinestomCommand command) { + // /zombies saveStatsHologram + var saveStatsHologramLit = ArgumentType.Literal("saveStatsHologram"); + var nameArg = ArgumentType.String("name"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + if (!(sender instanceof HypixelPlayer player)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + session.setStatsHologram(player.getPosition()); + sender.sendMessage("§aSet stats hologram position"); + }, saveStatsHologramLit, nameArg); + + // /zombies save + var saveLit = ArgumentType.Literal("save"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + session.saveToFile(); + sender.sendMessage("§aSaved configuration to file!"); + }, saveLit); + + // /zombies status + var statusLit = ArgumentType.Literal("status"); + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + MapConfigurationSession session = TypeZombiesConfiguratorLoader.getCurrentSession(); + if (session == null) { + sender.sendMessage("§cNo active session!"); + return; + } + sender.sendMessage("§e=== Zombies Map Configuration Status ==="); + sender.sendMessage("§7Map ID: §f" + session.getMapId()); + sender.sendMessage("§7Map Name: §f" + session.getMapName()); + sender.sendMessage("§7Map Type: §f" + session.getMapType().name()); + sender.sendMessage("§7Windows: §f" + session.getWindows().size()); + sender.sendMessage("§7Doors: §f" + session.getDoors().size()); + sender.sendMessage("§7Spawners: §f" + session.getSpawners().size()); + sender.sendMessage("§7Boss Spawns: §f" + session.getBossSpawns().size()); + sender.sendMessage("§7Shops: §f" + session.getShops().size()); + sender.sendMessage("§7Machines: §f" + session.getMachines().size()); + sender.sendMessage("§7Weapon Chests: §f" + session.getWeaponChests().size()); + sender.sendMessage("§7Team Machines: §f" + session.getTeamMachines().size()); + }, statusLit); + } + + private static String formatPos(Pos pos) { + return String.format("(%.1f, %.1f, %.1f)", pos.x(), pos.y(), pos.z()); + } +} diff --git a/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/events/ActionPlayerDataSpawn.java b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/events/ActionPlayerDataSpawn.java new file mode 100644 index 000000000..39e382ff6 --- /dev/null +++ b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/events/ActionPlayerDataSpawn.java @@ -0,0 +1,24 @@ +package net.swofty.type.zombiesconfigurator.events; + +import net.kyori.adventure.text.Component; +import net.minestom.server.event.player.PlayerSpawnEvent; +import net.swofty.type.generic.event.EventNodes; +import net.swofty.type.generic.event.HypixelEvent; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.user.HypixelPlayer; + +public class ActionPlayerDataSpawn implements HypixelEventClass { + + @HypixelEvent(node = EventNodes.PLAYER_DATA, requireDataLoaded = false, isAsync = true) + public void run(PlayerSpawnEvent event) { + if (!event.isFirstSpawn()) return; + + final HypixelPlayer player = (HypixelPlayer) event.getPlayer(); + + // Send welcome message + player.sendMessage(Component.text("§6§l=== Zombies Configurator ===")); + player.sendMessage(Component.text("§eUse §b/choosemap §eto load a map")); + player.sendMessage(Component.text("§eUse §b/zombiesconfig §eto configure the map")); + player.sendMessage(Component.text("§7Anvil worlds will be automatically converted to Polar format")); + } +} diff --git a/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/events/ActionPlayerJoin.java b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/events/ActionPlayerJoin.java new file mode 100644 index 000000000..d4b39cb78 --- /dev/null +++ b/type.zombiesconfigurator/src/main/java/net/swofty/type/zombiesconfigurator/events/ActionPlayerJoin.java @@ -0,0 +1,27 @@ +package net.swofty.type.zombiesconfigurator.events; + +import lombok.SneakyThrows; +import net.minestom.server.event.player.AsyncPlayerConfigurationEvent; +import net.swofty.type.generic.HypixelConst; +import net.swofty.type.generic.event.EventNodes; +import net.swofty.type.generic.event.HypixelEvent; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.user.HypixelPlayer; +import org.tinylog.Logger; + +public class ActionPlayerJoin implements HypixelEventClass { + + @SneakyThrows + @HypixelEvent(node = EventNodes.PLAYER, requireDataLoaded = false) + public void run(AsyncPlayerConfigurationEvent event) { + final HypixelPlayer player = (HypixelPlayer) event.getPlayer(); + + event.setSpawningInstance(HypixelConst.getEmptyInstance()); + Logger.info("Player " + player.getUsername() + " joined SkyWars Configurator"); + player.setRespawnPoint(HypixelConst.getTypeLoader() + .getLoaderValues() + .spawnPosition() + .apply(player.getOriginServer()) + ); + } +} diff --git a/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/BalanceConfigurations.java b/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/BalanceConfigurations.java index 07a9000be..6aa11089e 100644 --- a/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/BalanceConfigurations.java +++ b/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/BalanceConfigurations.java @@ -89,6 +89,16 @@ public class BalanceConfigurations { )), Map.entry(ServerType.MURDER_MYSTERY_CONFIGURATOR, List.of( new LowestPlayerCount() + )), + + Map.entry(ServerType.ARCADE_LOBBY, List.of( + new LowestPlayerCount() + )), + Map.entry(ServerType.ZOMBIES_GAME, List.of( + new LowestPlayerCount() + )), + Map.entry(ServerType.ZOMBIES_CONFIGURATOR, List.of( + new LowestPlayerCount() )) ));