diff --git a/build.gradle b/build.gradle index df8e22b..f8ec86a 100644 --- a/build.gradle +++ b/build.gradle @@ -12,15 +12,17 @@ base { repositories { maven { url 'https://repo.hypixel.net/repository/Hypixel/' } + maven { + name 'Xander Maven' + url 'https://maven.isxander.dev/releases' + } } loom { - splitEnvironmentSourceSets() mods { "zombies-utils" { sourceSet sourceSets.main - sourceSet sourceSets.client } } @@ -34,7 +36,10 @@ dependencies { // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - modImplementation 'net.hypixel:mod-api:1.0.1' + + // other deps + modImplementation 'net.hypixel:mod-api:1.0.1' + modImplementation "dev.isxander:yet-another-config-lib:${project.yacl_version}" } processResources { diff --git a/gradle.properties b/gradle.properties index 29518a5..aaf68a2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,3 +16,4 @@ archives_base_name=zombies-utils # Dependencies fabric_version=0.123.2+1.21.5 +yacl_version=3.6.6+1.21.5-fabric diff --git a/src/client/java/xyz/stachel/zombiesutils/ZombiesUtilsClient.java b/src/client/java/xyz/stachel/zombiesutils/ZombiesUtilsClient.java deleted file mode 100644 index 8e87530..0000000 --- a/src/client/java/xyz/stachel/zombiesutils/ZombiesUtilsClient.java +++ /dev/null @@ -1,20 +0,0 @@ -package xyz.stachel.zombiesutils; - -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.rendering.v1.HudLayerRegistrationCallback; -import net.hypixel.modapi.HypixelModAPI; -import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket; -import xyz.stachel.zombiesutils.handlers.Location; -import xyz.stachel.zombiesutils.handlers.Renderer; - -public class ZombiesUtilsClient implements ClientModInitializer { - - @Override - public void onInitializeClient() { - HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket.class); - HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket.class, Location::onLocation); - ZombiesUtils.LOGGER.info("initializing..."); - HudLayerRegistrationCallback.EVENT.register(new Renderer()); - } - -} diff --git a/src/client/resources/zombies-utils.client.mixins.json b/src/client/resources/zombies-utils.client.mixins.json deleted file mode 100644 index 72e38c2..0000000 --- a/src/client/resources/zombies-utils.client.mixins.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "required": true, - "package": "xyz.stachel.zombiesutils.mixin.client", - "compatibilityLevel": "JAVA_21", - "client": [ - "ClientPlayNetworkHandlerMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/src/main/java/xyz/stachel/zombiesutils/ZombiesUtils.java b/src/main/java/xyz/stachel/zombiesutils/ZombiesUtils.java index d049a78..0e7201a 100644 --- a/src/main/java/xyz/stachel/zombiesutils/ZombiesUtils.java +++ b/src/main/java/xyz/stachel/zombiesutils/ZombiesUtils.java @@ -1,17 +1,33 @@ package xyz.stachel.zombiesutils; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.client.rendering.v1.HudLayerRegistrationCallback; +import net.hypixel.modapi.HypixelModAPI; +import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import net.fabricmc.api.ModInitializer; +import xyz.stachel.zombiesutils.config.Config; +import xyz.stachel.zombiesutils.game.GameManager; +import xyz.stachel.zombiesutils.handlers.Location; +import xyz.stachel.zombiesutils.handlers.Renderer; public class ZombiesUtils implements ModInitializer { + private static final GameManager gameManager = new GameManager(); + + public static GameManager getGameManager() { + return gameManager; + } + public static final String MOD_ID = "zombies-utils"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); @Override public void onInitialize() { + HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket.class); + HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket.class, Location::onLocation); + ZombiesUtils.LOGGER.info("initializing..."); + HudLayerRegistrationCallback.EVENT.register(new Renderer()); + Config.readFile(); } - } diff --git a/src/main/java/xyz/stachel/zombiesutils/ZombiesUtilsClient.java b/src/main/java/xyz/stachel/zombiesutils/ZombiesUtilsClient.java new file mode 100644 index 0000000..2f71267 --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/ZombiesUtilsClient.java @@ -0,0 +1,37 @@ +package xyz.stachel.zombiesutils; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.lwjgl.glfw.GLFW; + +public class ZombiesUtilsClient implements ClientModInitializer { + public static boolean PLAYER_VISIBILITY_SWITCH; + + private static KeyBinding PLAYER_VISIBILITY_KEY; + + @Override + public void onInitializeClient() { + PLAYER_VISIBILITY_KEY = KeyBindingHelper.registerKeyBinding(new KeyBinding( + "key.zombies-utils.toggle_player_visibility", + InputUtil.Type.KEYSYM, + GLFW.GLFW_KEY_V, + "category.zombies-utils" + )); + ClientTickEvents.END_CLIENT_TICK.register(client -> { + while (PLAYER_VISIBILITY_KEY.wasPressed()) { + PLAYER_VISIBILITY_SWITCH = !PLAYER_VISIBILITY_SWITCH; + MutableText message = PLAYER_VISIBILITY_SWITCH ? + Text.translatable("key.zombies-utils.toggle_player_visibility.toggle_on").setStyle(Style.EMPTY.withColor(Formatting.GREEN).withBold(true)) : + Text.translatable("key.zombies-utils.toggle_player_visibility.toggle_off").setStyle(Style.EMPTY.withColor(Formatting.RED).withBold(true)); + client.player.sendMessage(message, false); + } + }); + } +} diff --git a/src/main/java/xyz/stachel/zombiesutils/config/PlayerVisibilityConfig.java b/src/main/java/xyz/stachel/zombiesutils/config/PlayerVisibilityConfig.java new file mode 100644 index 0000000..7a1ddb8 --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/config/PlayerVisibilityConfig.java @@ -0,0 +1,30 @@ +package xyz.stachel.zombiesutils.config; + +import dev.isxander.yacl3.api.ConfigCategory; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.OptionDescription; +import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.SerialEntry; +import net.minecraft.text.Text; + +public class PlayerVisibilityConfig { + + + @SerialEntry + private float range; + + ConfigCategory category(PlayerVisibilityConfig defaults) { + return ConfigCategory.createBuilder() + .name(Text.translatable("zombies-utils.config.player-visibility.name")) + .tooltip(Text.translatable("zombies-utils.config.player-visibility.tooltip")) + + .option(Option.createBuilder() + .name(Text.translatable("zombies-utils.config.player-visibility.range.name")) + .description(OptionDescription.of(Text.translatable("zombies-utils.config.player-visibility.range.tooltip"))) + .binding(defaults.range, () -> this.range, n -> {this.range = n;}) + .controller( o -> FloatSliderControllerBuilder.create(o).range(0f, 10f).step(0.01f)) + .build() + + ).build(); + } +} diff --git a/src/main/java/xyz/stachel/zombiesutils/config/ZombiesUtilsConfig.java b/src/main/java/xyz/stachel/zombiesutils/config/ZombiesUtilsConfig.java new file mode 100644 index 0000000..5de3382 --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/config/ZombiesUtilsConfig.java @@ -0,0 +1,38 @@ +package xyz.stachel.zombiesutils.config; + +import dev.isxander.yacl3.api.YetAnotherConfigLib; +import dev.isxander.yacl3.config.v2.api.ConfigClassHandler; +import dev.isxander.yacl3.config.v2.api.SerialEntry; +import dev.isxander.yacl3.config.v2.api.serializer.GsonConfigSerializerBuilder; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import xyz.stachel.zombiesutils.ZombiesUtils; + +public class ZombiesUtilsConfig { + private static final ConfigClassHandler configHandler = ConfigClassHandler + .createBuilder(ZombiesUtilsConfig.class) + .serializer(cfg -> GsonConfigSerializerBuilder.create(cfg).setPath(FabricLoader.getInstance().getConfigDir().resolve(ZombiesUtils.MOD_ID).resolve("config.json")).build()).build(); + + public ZombiesUtilsConfig() { + this.playerVis = new PlayerVisibilityConfig(); + } + + @SerialEntry + PlayerVisibilityConfig playerVis; + + public void display() { + Screen screen = YetAnotherConfigLib.create(configHandler, (defaults, current, b) -> b + .category(current.playerVis.category(defaults.playerVis))) + .generateScreen(null); + MinecraftClient.getInstance().setScreen(screen); + } + + public static void init() { + ZombiesUtilsConfig.configHandler.load(); + } + + public static ZombiesUtilsConfig getConfig() { + return ZombiesUtilsConfig.configHandler.instance(); + } +} diff --git a/src/main/java/xyz/stachel/zombiesutils/game/Game.java b/src/main/java/xyz/stachel/zombiesutils/game/Game.java index 1b27c72..1e2cd98 100644 --- a/src/main/java/xyz/stachel/zombiesutils/game/Game.java +++ b/src/main/java/xyz/stachel/zombiesutils/game/Game.java @@ -1,15 +1,24 @@ package xyz.stachel.zombiesutils.game; -import java.io.File; - +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.jetbrains.annotations.NotNull; +import xyz.stachel.zombiesutils.ZombiesUtils; +import xyz.stachel.zombiesutils.game.recorder.Category; +import xyz.stachel.zombiesutils.game.recorder.RecordMessageSender; +import xyz.stachel.zombiesutils.game.recorder.files.CategoryFile; class Game extends Timer { private final boolean joinedRoundOne; private int round; - private final File gameFile; - private String category; + private final GameFile gameFile; + private final boolean roundOneRecorded; + private Category category; public final GameMode mode; + private boolean escaping; public Game(@NotNull final GameFile gameFile, @NotNull final GameMode mode) { super(); @@ -17,6 +26,7 @@ class Game extends Timer { this.round = 1; this.joinedRoundOne = true; this.mode = mode; + this.roundOneRecorded = true; } public Game(@NotNull final GameFile gameFile, @NotNull final GameMode mode, final int round) { @@ -25,10 +35,75 @@ class Game extends Timer { this.round = round; this.joinedRoundOne = round == 1; this.mode = mode; + this.roundOneRecorded = round == 1; + } + + public void setCategory(Category category) { + this.category = category; + } + + public void pass(int round) { + if ((round == 0) || (this.round == round + 1) || (this.roundTime() < 100)) { + ZombiesUtils.LOGGER.debug("SPLIT CANCELLED"); + return; + } + try { + this.record(); + } catch (Exception e) { + ZombiesUtils.LOGGER.error(ExceptionUtils.getStackTrace(e)); + MinecraftClient.getInstance().player.sendMessage(Text.literal("Error recording splits").setStyle(Style.EMPTY.withColor(Formatting.RED)), false); + } + this.split(); + this.round = round + 1; } public long split(final int round) { - if (round ) + if (round == 1) { + + } return super.split(); } + + public void helicopter() { + if (!this.mode.isMap(GameMode.Map.PRISON)) { + MinecraftClient.getInstance().player.sendMessage(Text.literal("You are not imprisoned. Yet.").setStyle(Style.EMPTY.withColor(Formatting.RED)), false); + ZombiesUtils.LOGGER.error(Thread.currentThread().getStackTrace().toString()); + return; + } + this.escaping = true; + this.pass(30); + } + + private void record() { + this.compareSegment(); + if (this.roundOneRecorded) this.compareBest(); + this.gameFile.setSegment(this.round, (int) this.roundTime()); + } + + public void compareSegment() throws IndexOutOfBoundsException { + if (this.escaping) return; + final CategoryFile categoryFile = this.category.getByGameMode(this.mode); + final short bestSegment = categoryFile.getBestSegment(round); + final int roundTime = (int) this.roundTime(); + + if (bestSegment == (short) 0) categoryFile.setBestSegment(round, roundTime); + else if (roundTime < bestSegment) categoryFile.setBestSegment(round, roundTime); + final RecordMessageSender recordMessageSender = new RecordMessageSender(this.category.getName(), round, roundTime, bestSegment); + recordMessageSender.roundSplit(); + recordMessageSender.sendRecordMessage(); + } + + public void compareBest() throws IndexOutOfBoundsException { + final CategoryFile categoryFile = this.category.getByGameMode(this.mode); + final int round = this.escaping ? 31 : this.round; + final int personalBest = categoryFile.getPersonalBest(round); + final int gameTime = (int) this.gameTime(); + + if (personalBest == 0) categoryFile.setPersonalBest(round, gameTime); + else if (gameTime < personalBest) categoryFile.setPersonalBest(round, gameTime); + final RecordMessageSender recordMessageSender = new RecordMessageSender(category.getName(), round, gameTime, personalBest); + if (!escaping) recordMessageSender.gameSplit(); + else recordMessageSender.helicopterSplit(); + recordMessageSender.sendRecordMessage(); + } } diff --git a/src/main/java/xyz/stachel/zombiesutils/game/GameFile.java b/src/main/java/xyz/stachel/zombiesutils/game/GameFile.java index fc01920..5d398e6 100644 --- a/src/main/java/xyz/stachel/zombiesutils/game/GameFile.java +++ b/src/main/java/xyz/stachel/zombiesutils/game/GameFile.java @@ -1,5 +1,15 @@ package xyz.stachel.zombiesutils.game; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.jetbrains.annotations.NotNull; +import xyz.stachel.zombiesutils.ZombiesUtils; +import xyz.stachel.zombiesutils.game.recorder.FileManager; +import xyz.stachel.zombiesutils.game.recorder.data.GameData; + import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -7,13 +17,15 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import org.jetbrains.annotations.NotNull; - class GameFile extends File { - private FileWriter writer; + private final GameData data; + + private FileWriter writer; - GameFile(@NotNull final String serverNumber) { - super(new File("zombies", "runs"), formattedTime() + "_" + serverNumber + ".seg2"); + GameFile(@NotNull final String serverNumber, @NotNull final GameMode mode) { + super(new File("zombies", "runs"), formattedTime() + "_" + serverNumber + ".seg2"); + this.data = new GameData(mode.getMap()); + FileManager.createDataFile(this, this.data); } private static String formattedTime() { @@ -39,4 +51,15 @@ class GameFile extends File { void clean() throws IOException { this.writer.close(); } + + public void setSegment(int round, int ticks) { + this.data.setSegment(round - 1, ticks); + + try { + FileManager.writeDataToFile(this, this.data); + } catch (Exception e) { + ZombiesUtils.LOGGER.error(ExceptionUtils.getStackTrace(e)); + MinecraftClient.getInstance().player.sendMessage(Text.literal("Error saving segment to run-file. Please Contact Stachelbeere1248.").setStyle(Style.EMPTY.withColor(Formatting.RED)), false); + } + } } diff --git a/src/main/java/xyz/stachel/zombiesutils/game/GameManager.java b/src/main/java/xyz/stachel/zombiesutils/game/GameManager.java index 2e7c52a..6016950 100644 --- a/src/main/java/xyz/stachel/zombiesutils/game/GameManager.java +++ b/src/main/java/xyz/stachel/zombiesutils/game/GameManager.java @@ -1,27 +1,83 @@ package xyz.stachel.zombiesutils.game; -import java.util.HashMap; - -import xyz.stachel.zombiesutils.game.GameMode.Map; +import org.jetbrains.annotations.NotNull; import xyz.stachel.zombiesutils.handlers.Location; +import xyz.stachel.zombiesutils.game.GameMode.Map; +import xyz.stachel.zombiesutils.util.Utils; + +import java.util.HashMap; +import java.util.Optional; +import java.util.Set; public class GameManager { private final HashMap games = new HashMap<>(); + private Optional queuedDifficulty = Optional.empty(); + private String queuedDifficultyServer = "INVALID"; private void addGame(final String serverNumber, final GameMode mode, final int round) { - this.games.put(serverNumber, new Game(new GameFile(serverNumber), mode)); + if (serverNumber.equals(queuedDifficultyServer)) { + this.queuedDifficulty.ifPresent(mode::changeDifficulty); + } + this.queuedDifficulty = Optional.empty(); + this.games.put(serverNumber, new Game(new GameFile(serverNumber, mode), mode, round)); } public void onRound(final int round) { final String sn = Location.getServerNumber(); final String mode = Location.getMode(); - if (sn == null || mode == null || !mode.startsWith("ZOMBIES")) return; + if (sn == null || mode == null || !Utils.isZombies()) return; - if (!games.containsKey(sn)) addGame(sn, new GameMode(Map.DEAD_END), round); - else games.get(sn).split(round); + if (this.getGame(sn) == null) { + this.addGame(sn, new GameMode(Utils.getMap(mode)), round); + } else { + this.getGame(sn).split(round); + } } - public Game getGame() { - + public Game getGame(String sn) { + return this.games.get(sn); } + + public void endGame(final String serverNumber, final boolean isWin) { + if (!games.containsKey(serverNumber)) return; + final Game game = this.getGame(serverNumber); + if (isWin) { + switch (game.mode.getMap()) { + case DEAD_END: + case BAD_BLOOD: + case PRISON: + game.pass(30); + break; + case ALIEN_ARCADIUM: + game.pass(105); + break; + } + } + games.remove(serverNumber); + + } + + public void splitOrNew(GameMode mode, int round) { + final String serverNumber = Location.getServerNumber(); + if (games.containsKey(serverNumber)) { + if (round == 0) addGame(serverNumber, mode, round); + else this.getGame(serverNumber).pass(round); + } else addGame(serverNumber, mode, round); + } + + public void setDifficulty(@NotNull GameMode.Difficulty difficulty) { + this.queuedDifficultyServer = Location.getServerNumber(); + if (this.games.containsKey(this.queuedDifficultyServer)) { + this.getGame(this.queuedDifficultyServer).mode.changeDifficulty(difficulty); + } else this.queuedDifficulty = Optional.of(difficulty); + } + + public Set getGames() { + return this.games.keySet(); + } + + public void killAll() { + games.clear(); + } + } diff --git a/src/main/java/xyz/stachel/zombiesutils/game/GameMode.java b/src/main/java/xyz/stachel/zombiesutils/game/GameMode.java index ac8525a..9f665ce 100644 --- a/src/main/java/xyz/stachel/zombiesutils/game/GameMode.java +++ b/src/main/java/xyz/stachel/zombiesutils/game/GameMode.java @@ -2,7 +2,7 @@ package xyz.stachel.zombiesutils.game; import org.jetbrains.annotations.NotNull; -class GameMode { +public class GameMode { private final Map map; private Difficulty difficulty; @@ -19,11 +19,23 @@ class GameMode { this.difficulty = map != Map.ALIEN_ARCADIUM ? difficulty : Difficulty.NORMAL; } - enum Map { + public Map getMap() { + return this.map; + } + + public Difficulty getDifficulty() { + return this.difficulty; + } + + public boolean isMap(Map map) { + return map == this.map; + } + + public enum Map { DEAD_END, BAD_BLOOD, PRISON, ALIEN_ARCADIUM; } - enum Difficulty { + public enum Difficulty { NORMAL, HARD, RIP; } diff --git a/src/main/java/xyz/stachel/zombiesutils/game/Prefix.java b/src/main/java/xyz/stachel/zombiesutils/game/Prefix.java new file mode 100644 index 0000000..8b25f58 --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/game/Prefix.java @@ -0,0 +1,36 @@ +package xyz.stachel.zombiesutils.game; + +public enum Prefix { + BOSS(0xCC5555, "B", 0x7A3333), + BLAZES(0xEFB61F, "BL", 0x8F6D0F), + SLIME(0x88FF88, "S", 0x51A951), + HBM(0x2A415F, "HBM", 0x193241), + WITHER_SKELETON(0x888888, "WS", 0x515151), + OLD_ONE(0x55AA55, "O1", 0x336633), + GIANT(0x00FFFF, "G", 0x009999), + POLICE(0x16537E, "P", 0x0E324D), + CELL(0xFF8234, "C", 0x99501F), + WINDOW(0xAAAAAA, "W", 0x666666); + + private final int color; + private final int fadedColor; + private final String prefix; + + Prefix(final int color, final String prefix, final int fadedColor) { + this.color = color; + this.prefix = prefix; + this.fadedColor = fadedColor; + } + + public int getColor() { + return color; + } + + public String getPrefix() { + return prefix; + } + + public int getFadedColor() { + return fadedColor; + } +} diff --git a/src/main/java/xyz/stachel/zombiesutils/game/recorder/Category.java b/src/main/java/xyz/stachel/zombiesutils/game/recorder/Category.java new file mode 100644 index 0000000..dd58ed1 --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/game/recorder/Category.java @@ -0,0 +1,76 @@ +package xyz.stachel.zombiesutils.game.recorder; + +import org.jetbrains.annotations.NotNull; +import xyz.stachel.zombiesutils.ZombiesUtils; +import xyz.stachel.zombiesutils.game.GameMode; +import xyz.stachel.zombiesutils.game.recorder.files.CategoryFile; +import xyz.stachel.zombiesutils.util.Utils; + +import java.io.File; + +import static xyz.stachel.zombiesutils.game.GameMode.Map.*; + +public class Category { + private static String selectedCategory /*= ZombiesUtils.getInstance().getConfig().getDefaultCategory()*/; + public final CategoryFile[] categoryFiles = new CategoryFile[10]; + private final String name; + + public Category() { + final File category; + if (Utils.isHypixel()) category = new File(new File("zombies", "splits"), selectedCategory); + else category = new File(new File("zombies", "practise-splits"), selectedCategory); + categoryFiles[0] = new CategoryFile(category, DEAD_END, GameMode.Difficulty.NORMAL); + categoryFiles[1] = new CategoryFile(category, DEAD_END, GameMode.Difficulty.HARD); + categoryFiles[2] = new CategoryFile(category, DEAD_END, GameMode.Difficulty.RIP); + + categoryFiles[3] = new CategoryFile(category, BAD_BLOOD, GameMode.Difficulty.NORMAL); + categoryFiles[4] = new CategoryFile(category, BAD_BLOOD, GameMode.Difficulty.HARD); + categoryFiles[5] = new CategoryFile(category, BAD_BLOOD, GameMode.Difficulty.RIP); + + categoryFiles[6] = new CategoryFile(category, ALIEN_ARCADIUM, GameMode.Difficulty.NORMAL); + + categoryFiles[7] = new CategoryFile(category, PRISON, GameMode.Difficulty.NORMAL); + categoryFiles[8] = new CategoryFile(category, PRISON, GameMode.Difficulty.HARD); + categoryFiles[9] = new CategoryFile(category, PRISON, GameMode.Difficulty.RIP); + + this.name = Category.selectedCategory; + } + + public static void setSelectedCategory(String selectedCategory) { + Category.selectedCategory = selectedCategory; + // ZombiesUtils.getGameManager().getGame().ifPresent(game -> game.setCategory(new Category())); + } + + public static String[] getCategories() { + File dir; + if (Utils.isHypixel()) dir = new File("zombies" + File.separator + "splits"); + else dir = new File("zombies" + File.separator + "practise-splits"); + if (dir.isDirectory()) return dir.list(); + else return new String[0]; + } + + public CategoryFile getByGameMode(@NotNull GameMode gameMode) { + return switch (gameMode.getMap()) { + case DEAD_END -> switch (gameMode.getDifficulty()) { + case NORMAL -> categoryFiles[0]; + case HARD -> categoryFiles[1]; + case RIP -> categoryFiles[2]; + }; + case BAD_BLOOD -> switch (gameMode.getDifficulty()) { + case NORMAL -> categoryFiles[3]; + case HARD -> categoryFiles[4]; + case RIP -> categoryFiles[5]; + }; + case ALIEN_ARCADIUM -> categoryFiles[6]; + case PRISON -> switch (gameMode.getDifficulty()) { + case NORMAL -> categoryFiles[7]; + case HARD -> categoryFiles[8]; + case RIP -> categoryFiles[9]; + }; + }; + } + + public String getName() { + return name; + } +} \ No newline at end of file diff --git a/src/main/java/xyz/stachel/zombiesutils/game/recorder/FileManager.java b/src/main/java/xyz/stachel/zombiesutils/game/recorder/FileManager.java new file mode 100644 index 0000000..fd5e184 --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/game/recorder/FileManager.java @@ -0,0 +1,58 @@ +package xyz.stachel.zombiesutils.game.recorder; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import org.apache.commons.io.FileUtils; +import org.jetbrains.annotations.NotNull; +import xyz.stachel.zombiesutils.game.recorder.data.CategoryData; +import xyz.stachel.zombiesutils.game.recorder.files.CategoryFile; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class FileManager { + private static CategoryData readDataFromFile(@NotNull File file) throws FileNotFoundException, JsonSyntaxException { + if (!file.exists()) throw new FileNotFoundException(); + + String dataJson; + Gson gson = new Gson(); + try { + dataJson = FileUtils.readFileToString(file, StandardCharsets.UTF_16); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (dataJson == null || dataJson.trim().isEmpty()) throw new JsonSyntaxException("File empty"); + + return gson.fromJson(dataJson, CategoryData.class); + } + + public static void createDataFile(@NotNull File file, ISplitsData data) { + try { + //noinspection ResultOfMethodCallIgnored + file.getParentFile().mkdirs(); + //noinspection ResultOfMethodCallIgnored + file.createNewFile(); + writeDataToFile(file, data); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void writeDataToFile(File file, @NotNull ISplitsData data) throws IOException { + FileUtils.writeStringToFile(file, data.toJSON(), StandardCharsets.UTF_16); + } + + @NotNull + public static CategoryData categoryReadOrCreate(CategoryFile file) { + CategoryData data; + try { + data = FileManager.readDataFromFile(file); + } catch (FileNotFoundException | JsonSyntaxException ignored) { + data = new CategoryData(file.getMap()); + FileManager.createDataFile(file, data); + } + return data; + } +} \ No newline at end of file diff --git a/src/main/java/xyz/stachel/zombiesutils/game/recorder/ISplitsData.java b/src/main/java/xyz/stachel/zombiesutils/game/recorder/ISplitsData.java new file mode 100644 index 0000000..28027fd --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/game/recorder/ISplitsData.java @@ -0,0 +1,5 @@ +package xyz.stachel.zombiesutils.game.recorder; + +public interface ISplitsData { + String toJSON(); +} \ No newline at end of file diff --git a/src/main/java/xyz/stachel/zombiesutils/game/recorder/RecordMessageSender.java b/src/main/java/xyz/stachel/zombiesutils/game/recorder/RecordMessageSender.java new file mode 100644 index 0000000..38dbfda --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/game/recorder/RecordMessageSender.java @@ -0,0 +1,54 @@ +package xyz.stachel.zombiesutils.game.recorder; + +import org.jetbrains.annotations.Contract; + +public class RecordMessageSender { + + private final StringBuilder recordMessage; + private final int newTime; + private final int oldTime; + private final int round; + private final String deltaString; + private final String timeString; + private String copyString; + + public RecordMessageSender(final String categoryName, final int round, final int newTime, final int oldTime) { + this.recordMessage = new StringBuilder( + "§l§a▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬\n§e Category: §d" + categoryName + ); + this.newTime = newTime; + this.oldTime = oldTime; + this.deltaString = oldTime != 0 ? " " + formattedDelta(newTime, oldTime) : ""; + this.timeString = formattedTime(newTime); + this.round = round; + } + + public void sendRecordMessage() { + } + + public void gameSplit() { + } + + public void roundSplit() { + } + + public void helicopterSplit() { + } + + @Contract(pure = true) + private String formattedTime(int time) { + time *= 50; + return String.format("%d:%02d.%d%d", + time / 60000, + (time % 60000) / 1000, + (time % 1000) / 100, + (time % 100) / 10 + ); + } + + @Contract(pure = true) + private String formattedDelta(int newTime, int oldTime) { + final double delta = (double) (newTime - oldTime) / 20; + return String.format("%+.2f", delta); + } +} diff --git a/src/main/java/xyz/stachel/zombiesutils/game/recorder/data/CategoryData.java b/src/main/java/xyz/stachel/zombiesutils/game/recorder/data/CategoryData.java new file mode 100644 index 0000000..10379fb --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/game/recorder/data/CategoryData.java @@ -0,0 +1,58 @@ +package xyz.stachel.zombiesutils.game.recorder.data; + +import com.google.gson.Gson; +import org.jetbrains.annotations.NotNull; +import xyz.stachel.zombiesutils.game.GameMode; +import xyz.stachel.zombiesutils.game.recorder.ISplitsData; + +import java.util.Arrays; + +public class CategoryData implements ISplitsData { + private final short[] bestSegments; //in ticks, max ~27 min + private final int[] personalBests; //in ticks, + + public CategoryData(@NotNull GameMode.Map map) throws IllegalStateException { + switch (map) { + case ALIEN_ARCADIUM: + bestSegments = new short[105]; + personalBests = new int[105]; + break; + case DEAD_END: + case BAD_BLOOD: + bestSegments = new short[30]; + personalBests = new int[30]; + break; + case PRISON: + bestSegments = new short[30]; + personalBests = new int[31]; + break; + default: + throw new IllegalStateException("Not a map: " + map); + } + Arrays.fill(bestSegments, (short) 0); + Arrays.fill(personalBests, 0); + } + + @Override + @NotNull + public String toJSON() { + Gson gson = new Gson(); + return gson.toJson(this, CategoryData.class); + } + + public short getBestSegment(int index) { + return bestSegments[index]; + } + + public int getPersonalBest(int index) { + return personalBests[index]; + } + + public void setBestSegment(int index, int ticks) { + bestSegments[index] = (short) ticks; + } + + public void setPersonalBest(int index, int ticks) { + personalBests[index] = ticks; + } +} diff --git a/src/main/java/xyz/stachel/zombiesutils/game/recorder/data/GameData.java b/src/main/java/xyz/stachel/zombiesutils/game/recorder/data/GameData.java new file mode 100644 index 0000000..ebf996f --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/game/recorder/data/GameData.java @@ -0,0 +1,41 @@ +package xyz.stachel.zombiesutils.game.recorder.data; + +import com.google.gson.Gson; +import org.jetbrains.annotations.NotNull; +import xyz.stachel.zombiesutils.game.GameMode; +import xyz.stachel.zombiesutils.game.recorder.ISplitsData; + +import java.util.Arrays; + +public class GameData implements ISplitsData { + private final short[] segments; + + public GameData(@NotNull GameMode.Map map) throws IllegalStateException { + switch (map) { + case ALIEN_ARCADIUM: + segments = new short[105]; + break; + case DEAD_END: + case BAD_BLOOD: + segments = new short[30]; + break; + case PRISON: + segments = new short[31]; + break; + default: + throw new IllegalStateException("Not a map: " + map); + } + Arrays.fill(segments, (short) 0); + } + + @Override + @NotNull + public String toJSON() { + Gson gson = new Gson(); + return gson.toJson(this.segments); + } + + public void setSegment(int index, int ticks) { + segments[index] = (short) ticks; + } +} diff --git a/src/main/java/xyz/stachel/zombiesutils/game/recorder/files/CategoryFile.java b/src/main/java/xyz/stachel/zombiesutils/game/recorder/files/CategoryFile.java new file mode 100644 index 0000000..6adcbb2 --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/game/recorder/files/CategoryFile.java @@ -0,0 +1,68 @@ +package xyz.stachel.zombiesutils.game.recorder.files; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.jetbrains.annotations.NotNull; +import xyz.stachel.zombiesutils.ZombiesUtils; +import xyz.stachel.zombiesutils.game.GameMode; +import xyz.stachel.zombiesutils.game.recorder.FileManager; +import xyz.stachel.zombiesutils.game.recorder.data.CategoryData; + +import java.io.File; + +public class CategoryFile extends File { + private final CategoryData data; + private final GameMode.Map map; + private final GameMode.Difficulty difficulty; + + public CategoryFile(File category, @NotNull GameMode.Map map, GameMode.Difficulty difficulty) { + // Game-directory -> custom category -> file named "MAP_DIFFICULTY.times" + // Content encoded in StandardCharsets.UTF_16 + super(category, map + "_" + difficulty + ".times"); + this.map = map; + this.difficulty = difficulty; + this.data = FileManager.categoryReadOrCreate(this); + } + + public short getBestSegment(int round) { + return this.data.getBestSegment(round - 1); + } + + public void setBestSegment(int round, int ticks) { + this.data.setBestSegment(round - 1, ticks); + + try { + FileManager.writeDataToFile(this, this.data); + } catch (Exception e) { + ZombiesUtils.LOGGER.error(ExceptionUtils.getStackTrace(e)); + MinecraftClient.getInstance().player.sendMessage(Text.literal("Error saving segment to splits-file. Please Contact Stachelbeere1248.").setStyle(Style.EMPTY.withColor(Formatting.RED)), false); + } + } + + public int getPersonalBest(int round) { + return data.getPersonalBest(round - 1); + } + + public void setPersonalBest(int round, int ticks) { + this.data.setPersonalBest(round - 1, ticks); + + try { + FileManager.writeDataToFile(this, this.data); + } catch (Exception e) { + ZombiesUtils.LOGGER.error(ExceptionUtils.getStackTrace(e)); + MinecraftClient.getInstance().player.sendMessage(Text.literal("Error saving pb to splits-file. Please Contact Stachelbeere1248.").setStyle(Style.EMPTY.withColor(Formatting.RED)), false); + } + } + + public GameMode.Map getMap() { + return this.map; + } + + public GameMode.Difficulty getDifficulty() { + return this.difficulty; + } + +} \ No newline at end of file diff --git a/src/client/java/xyz/stachel/zombiesutils/handlers/Location.java b/src/main/java/xyz/stachel/zombiesutils/handlers/Location.java similarity index 83% rename from src/client/java/xyz/stachel/zombiesutils/handlers/Location.java rename to src/main/java/xyz/stachel/zombiesutils/handlers/Location.java index 791f8bd..b98d7ad 100644 --- a/src/client/java/xyz/stachel/zombiesutils/handlers/Location.java +++ b/src/main/java/xyz/stachel/zombiesutils/handlers/Location.java @@ -1,18 +1,18 @@ package xyz.stachel.zombiesutils.handlers; -import java.util.Optional; - import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket; +import java.util.Optional; + public class Location { private static String serverNumber; private static String mode; public static void onLocation(ClientboundLocationPacket p) { - Location.serverNumber = p.getServerName(); + serverNumber = p.getServerName(); Optional m = p.getMode(); - if (m.isPresent()) Location.mode = m.get(); + m.ifPresent(s -> mode = s); } public static String getServerNumber() { diff --git a/src/client/java/xyz/stachel/zombiesutils/handlers/Renderer.java b/src/main/java/xyz/stachel/zombiesutils/handlers/Renderer.java similarity index 100% rename from src/client/java/xyz/stachel/zombiesutils/handlers/Renderer.java rename to src/main/java/xyz/stachel/zombiesutils/handlers/Renderer.java diff --git a/src/client/java/xyz/stachel/zombiesutils/mixin/client/ClientPlayNetworkHandlerMixin.java b/src/main/java/xyz/stachel/zombiesutils/mixin/ClientPlayNetworkHandlerMixin.java similarity index 52% rename from src/client/java/xyz/stachel/zombiesutils/mixin/client/ClientPlayNetworkHandlerMixin.java rename to src/main/java/xyz/stachel/zombiesutils/mixin/ClientPlayNetworkHandlerMixin.java index 9fa8faf..d61182f 100644 --- a/src/client/java/xyz/stachel/zombiesutils/mixin/client/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/xyz/stachel/zombiesutils/mixin/ClientPlayNetworkHandlerMixin.java @@ -1,30 +1,32 @@ -package xyz.stachel.zombiesutils.mixin.client; +package xyz.stachel.zombiesutils.mixin; + +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.packet.s2c.play.TitleS2CPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import xyz.stachel.zombiesutils.ZombiesUtils; +import xyz.stachel.zombiesutils.game.GameManager; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.network.packet.s2c.play.TitleS2CPacket; -import xyz.stachel.zombiesutils.game.GameManager; - @Mixin(ClientPlayNetworkHandler.class) -public class ClientPlayNetworkHandlerMixin { +public abstract class ClientPlayNetworkHandlerMixin { + @Unique private static final Pattern pattern = Pattern.compile("Round (\\d{1,3})"); @Inject(at = @At("HEAD"), method = "onTitle") - private void zombiesUtils_onTitle(TitleS2CPacket p, CallbackInfo info) { - final String msg = p.text().getString(); - final Matcher matcher = pattern.matcher(msg); - if (matcher.find()) { - int number = Integer.parseInt(matcher.group(1)); - if (number >= 1 && number <= 105) { - GameManager.onRound(number); + private void zombiesUtils_onTitle(TitleS2CPacket p, CallbackInfo info) { + final String msg = p.text().getString(); + final Matcher matcher = pattern.matcher(msg); + if (matcher.find()) { + int number = Integer.parseInt(matcher.group(1)); + if (number >= 1 && number <= 105) { + ZombiesUtils.getGameManager().onRound(number); + } } } - } } diff --git a/src/main/java/xyz/stachel/zombiesutils/mixin/OtherClientPlayerEntityMixin.java b/src/main/java/xyz/stachel/zombiesutils/mixin/OtherClientPlayerEntityMixin.java new file mode 100644 index 0000000..5cd713a --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/mixin/OtherClientPlayerEntityMixin.java @@ -0,0 +1,31 @@ +package xyz.stachel.zombiesutils.mixin; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.mojang.authlib.GameProfile; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.network.OtherClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import xyz.stachel.zombiesutils.ZombiesUtilsClient; +import xyz.stachel.zombiesutils.config.Configs; + +@Mixin(OtherClientPlayerEntity.class) +public abstract class OtherClientPlayerEntityMixin extends AbstractClientPlayerEntity { + + public OtherClientPlayerEntityMixin(ClientWorld world, GameProfile profile) { + super(world, profile); + } + + @ModifyReturnValue(method = "shouldRender", at = @At(value = "RETURN")) + public boolean shouldRender(boolean original, double distance) { + if (ZombiesUtilsClient.PLAYER_VISIBILITY_SWITCH) { + double dist = Configs.playerVisibleDistance.getValue(); + double d = this.getBoundingBox().getAverageSideLength() + dist; + + return (distance >= d * d) && original; + } else { + return original; + } + } +} diff --git a/src/main/java/xyz/stachel/zombiesutils/util/LanguageSupport.java b/src/main/java/xyz/stachel/zombiesutils/util/LanguageSupport.java new file mode 100644 index 0000000..0506073 --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/util/LanguageSupport.java @@ -0,0 +1,78 @@ +package xyz.stachel.zombiesutils.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.regex.Pattern; + +@SuppressWarnings("SpellCheckingInspection") +public class LanguageSupport { + private static final String[] LANGUAGES = { + "EN", + "FR", + "DE" + }; + + public static boolean isLoss(@NotNull String input) { + final String[] words = { + "§cGame Over!", + "§cPartie terminée!", + "§cDas Spiel ist vorbei!" + }; + return Arrays.asList(words).contains(input); + } + + public static boolean isWin(@NotNull String input) { + final String[] words = { + "§aYou Win!", + "§aVous avez gagné!", + "§aDu gewinnst!" + }; + return Arrays.asList(words).contains(input); + } + + public static boolean containsHard(@NotNull String input) { + final String[] words = { + "Hard Difficulty", + "Difficulté Hard", + "Hard Schwierigkeitsgrad", + "困难", + "困難" + }; + return Arrays.stream(words).anyMatch(input::contains); + } + + public static boolean containsRIP(@NotNull String input) { + final String[] words = { + "RIP Difficulty", + "Difficulté RIP", + "RIP Schwierigkeitsgrad", + "安息" + }; + return Arrays.stream(words).anyMatch(input::contains); + } + + public static boolean isHelicopterIncoming(@NotNull String input) { + final String[] words = { + "The Helicopter is on its way! Hold out for 120 more seconds!" + }; + return Arrays.stream(words).anyMatch(input::contains); + } + + public static @NotNull Pattern roundPattern(@NotNull String language) { + switch (language) { + case "EN": + return Pattern.compile("Round ([0-9]{1,3})"); + case "FR": + return Pattern.compile("Manche ([0-9]{1,3})"); + case "DE": + return Pattern.compile("Runde ([0-9]{1,3})"); + default: + throw new IllegalStateException("Unexpected value: " + language); + } + } + + public static String[] getLanguages() { + return LANGUAGES; + } +} \ No newline at end of file diff --git a/src/main/java/xyz/stachel/zombiesutils/util/Utils.java b/src/main/java/xyz/stachel/zombiesutils/util/Utils.java new file mode 100644 index 0000000..649e705 --- /dev/null +++ b/src/main/java/xyz/stachel/zombiesutils/util/Utils.java @@ -0,0 +1,30 @@ +package xyz.stachel.zombiesutils.util; + +import org.jetbrains.annotations.Nullable; +import xyz.stachel.zombiesutils.game.GameMode; +import xyz.stachel.zombiesutils.handlers.Location; + +import static xyz.stachel.zombiesutils.game.GameMode.Map.*; + +public class Utils { + + @Nullable + public static GameMode.Map getMap(String mode) { + return switch (mode) { + case "ZOMBIES_DEAD_END" -> DEAD_END; + case "ZOMBIES_BAD_BLOOD" -> BAD_BLOOD; + case "ZOMBIES_ALIEN_ARCADIUM" -> ALIEN_ARCADIUM; + case "ZOMBIES_PRISON" -> PRISON; + default -> null; + }; + } + + public static boolean isZombies() { + return Location.getMode().startsWith("ZOMBIES"); + } + + public static boolean isHypixel() { + return true; + } + +} diff --git a/src/main/resources/assets/zombies-utils/lang/en_us.json b/src/main/resources/assets/zombies-utils/lang/en_us.json new file mode 100644 index 0000000..a407743 --- /dev/null +++ b/src/main/resources/assets/zombies-utils/lang/en_us.json @@ -0,0 +1,6 @@ +{ + "category.zombies-utils": "Zombies Utils", + "key.zombies-utils.toggle_player_visibility": "Toggle Nearest Players", + "key.zombies-utils.toggle_player_visibility.toggle_on": "Nearest Players Are Now Invisible", + "key.zombies-utils.toggle_player_visibility.toggle_off": "Nearest Players Are Now Visible" +} \ No newline at end of file diff --git a/src/main/resources/assets/zombies-utils/lang/ru_ru.json b/src/main/resources/assets/zombies-utils/lang/ru_ru.json new file mode 100644 index 0000000..9566dab --- /dev/null +++ b/src/main/resources/assets/zombies-utils/lang/ru_ru.json @@ -0,0 +1,6 @@ +{ + "category.zombies-utils": "Zombies Utils", + "key.zombies-utils.toggle_player_visibility": "Видимость ближайших игроков", + "key.zombies-utils.toggle_player_visibility.toggle_on": "Ближайшие игроки к вам теперь невидимы.", + "key.zombies-utils.toggle_player_visibility.toggle_off": "Ближайшие игроки к вам теперь видимы." +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 480c9c6..5235bee 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,7 +24,7 @@ }, "mixins": [ { - "config": "zombies-utils.client.mixins.json", + "config": "zombies-utils.mixins.json", "environment": "client" } ], @@ -33,7 +33,8 @@ "minecraft": "~1.21.5", "java": ">=21", "fabric-api": "*", - "hypixel-mod-api": ">=1.0.1" + "hypixel-mod-api": ">=1.0.1", + "yet_another_config_lib_v3": ">=3.6.6" }, "suggests": {} } diff --git a/src/main/resources/zombies-utils.mixins.json b/src/main/resources/zombies-utils.mixins.json new file mode 100644 index 0000000..4d05fdd --- /dev/null +++ b/src/main/resources/zombies-utils.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "xyz.stachel.zombiesutils.mixin", + "compatibilityLevel": "JAVA_21", + "client": [ + "ClientPlayNetworkHandlerMixin", + "OtherClientPlayerEntityMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "mixins": [] +}