From 6b9c2dfa061f1341a0387bbe89be83c5d8a305da Mon Sep 17 00:00:00 2001 From: Kampfmietze Date: Mon, 4 Aug 2025 14:34:43 +0200 Subject: [PATCH] push --- .vscode/launch.json | 4 +- build.gradle.kts | 81 +--- gradle.properties | 6 +- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 4 - src/main/java/de/torui/coflsky/CoflSky.java | 72 ++- .../java/de/torui/coflsky/CoflSkyCommand.java | 175 ++++++- .../java/de/torui/coflsky/FlipHandler.java | 126 +++++ .../de/torui/coflsky/WSCommandHandler.java | 184 ++++--- .../coflsky/commands/CoflGuiCommand.java | 61 +++ .../de/torui/coflsky/commands/Command.java | 1 - .../torui/coflsky/commands/CommandType.java | 94 ++++ .../coflsky/commands/JsonStringCommand.java | 30 ++ .../de/torui/coflsky/commands/RawCommand.java | 33 ++ .../coflsky/commands/models/AuctionData.java | 30 ++ .../commands/models/ChatMessageData.java | 25 + .../coflsky/commands/models/FlipData.java | 34 ++ .../commands/models/HotkeyRegister.java | 14 + .../coflsky/commands/models/ModListData.java | 33 ++ .../coflsky/commands/models/ProxyRequest.java | 31 ++ .../coflsky/commands/models/SoundData.java | 23 + .../coflsky/commands/models/TimerData.java | 22 + .../de/torui/coflsky/config/CoflConfig.java | 436 +++++++++++++++++ .../torui/coflsky/config/SettingsCache.java | 58 +++ .../coflsky/configuration/Configuration.java | 46 ++ .../configuration/ConfigurationManager.java | 107 +++++ .../coflsky/configuration/Description.java | 20 + .../coflsky/configuration/LocalConfig.java | 4 +- .../coflsky/dynamic/ConfigClassGenerator.java | 106 ++++ .../java/de/torui/coflsky/gui/GUIType.java | 6 + .../coflsky/gui/bingui/BinGuiCurrent.java | 4 +- .../coflsky/gui/bingui/BinGuiManager.java | 6 +- .../gui/bingui/helper/RenderUtils.java | 69 --- .../torui/coflsky/gui/tfm/ButtonRemapper.java | 6 +- ...onHandler.java => DescriptionHandler.java} | 95 ++-- .../torui/coflsky/handlers/EventHandler.java | 47 +- .../torui/coflsky/handlers/EventRegistry.java | 222 +++------ .../init/AutoDiscoveryMixinPlugin.java | 188 -------- .../torui/coflsky/listeners/ChatListener.java | 5 + .../listeners/OneConfigOpenListener.java | 41 ++ .../CoflSessionManager.java | 145 ++++++ .../minecraft_integration/CountdownTimer.java | 7 +- .../PlayerDataProvider.java | 2 +- .../coflsky/mixins/AccessorGuiEditSign.java | 12 - .../coflsky/network/QueryServerCommands.java | 58 +++ .../de/torui/coflsky/network/WSClient.java | 144 ++++++ .../coflsky/network/WSClientWrapper.java | 173 +++++++ .../de/torui/coflsky/proxy/APIKeyManager.java | 54 +++ .../de/torui/coflsky/proxy/ProxyManager.java | 95 ++++ .../coflsky/util/ServerSettingsLoader.java | 452 ++++++++++++++++++ src/main/resources/accesstransformer.cfg | 4 - src/main/resources/mixins.skycofl.json | 8 - 52 files changed, 2983 insertions(+), 722 deletions(-) create mode 100644 src/main/java/de/torui/coflsky/FlipHandler.java create mode 100644 src/main/java/de/torui/coflsky/commands/CoflGuiCommand.java create mode 100644 src/main/java/de/torui/coflsky/commands/CommandType.java create mode 100644 src/main/java/de/torui/coflsky/commands/JsonStringCommand.java create mode 100644 src/main/java/de/torui/coflsky/commands/RawCommand.java create mode 100644 src/main/java/de/torui/coflsky/commands/models/AuctionData.java create mode 100644 src/main/java/de/torui/coflsky/commands/models/ChatMessageData.java create mode 100644 src/main/java/de/torui/coflsky/commands/models/FlipData.java create mode 100644 src/main/java/de/torui/coflsky/commands/models/HotkeyRegister.java create mode 100644 src/main/java/de/torui/coflsky/commands/models/ModListData.java create mode 100644 src/main/java/de/torui/coflsky/commands/models/ProxyRequest.java create mode 100644 src/main/java/de/torui/coflsky/commands/models/SoundData.java create mode 100644 src/main/java/de/torui/coflsky/commands/models/TimerData.java create mode 100644 src/main/java/de/torui/coflsky/config/CoflConfig.java create mode 100644 src/main/java/de/torui/coflsky/config/SettingsCache.java create mode 100644 src/main/java/de/torui/coflsky/configuration/Configuration.java create mode 100644 src/main/java/de/torui/coflsky/configuration/ConfigurationManager.java create mode 100644 src/main/java/de/torui/coflsky/configuration/Description.java create mode 100644 src/main/java/de/torui/coflsky/dynamic/ConfigClassGenerator.java create mode 100644 src/main/java/de/torui/coflsky/gui/GUIType.java rename src/main/java/de/torui/coflsky/handlers/{ForgeDescriptionHandler.java => DescriptionHandler.java} (76%) delete mode 100644 src/main/java/de/torui/coflsky/init/AutoDiscoveryMixinPlugin.java create mode 100644 src/main/java/de/torui/coflsky/listeners/OneConfigOpenListener.java create mode 100644 src/main/java/de/torui/coflsky/minecraft_integration/CoflSessionManager.java delete mode 100644 src/main/java/de/torui/coflsky/mixins/AccessorGuiEditSign.java create mode 100644 src/main/java/de/torui/coflsky/network/QueryServerCommands.java create mode 100644 src/main/java/de/torui/coflsky/network/WSClient.java create mode 100644 src/main/java/de/torui/coflsky/network/WSClientWrapper.java create mode 100644 src/main/java/de/torui/coflsky/proxy/APIKeyManager.java create mode 100644 src/main/java/de/torui/coflsky/proxy/ProxyManager.java create mode 100644 src/main/java/de/torui/coflsky/util/ServerSettingsLoader.java delete mode 100644 src/main/resources/accesstransformer.cfg delete mode 100644 src/main/resources/mixins.skycofl.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 19c0552..ce0e818 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "console": "internalConsole", "stopOnEntry": false, "mainClass": "net.fabricmc.devlaunchinjector.Main", - "vmArgs": "\"-Dfabric.dli.config\u003dE:\\CoflWork\\SkyblockMod\\.gradle\\loom-cache\\launch.cfg\" \"-Dfabric.dli.env\u003dclient\" \"-Dfabric.dli.main\u003dnet.minecraft.launchwrapper.Launch\"", + "vmArgs": "\"-Dfabric.dli.config\u003dC:\\Coding\\Cofl\\SkyCofl-GUI\\.gradle\\loom-cache\\launch.cfg\" \"-Dfabric.dli.env\u003dclient\" \"-Dfabric.dli.main\u003dnet.minecraft.launchwrapper.Launch\"", "args": "", "env": {}, "projectName": "" @@ -22,7 +22,7 @@ "console": "internalConsole", "stopOnEntry": false, "mainClass": "net.fabricmc.devlaunchinjector.Main", - "vmArgs": "\"-Dfabric.dli.config\u003dE:\\CoflWork\\SkyblockMod\\.gradle\\loom-cache\\launch.cfg\" \"-Dfabric.dli.env\u003dserver\" \"-Dfabric.dli.main\u003dnet.minecraft.launchwrapper.Launch\"", + "vmArgs": "\"-Dfabric.dli.config\u003dC:\\Coding\\Cofl\\SkyCofl-GUI\\.gradle\\loom-cache\\launch.cfg\" \"-Dfabric.dli.env\u003dserver\" \"-Dfabric.dli.main\u003dnet.minecraft.launchwrapper.Launch\"", "args": "\"nogui\"", "env": {}, "projectName": "" diff --git a/build.gradle.kts b/build.gradle.kts index 3a89a41..92d6c36 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ -import org.apache.commons.lang3.SystemUtils plugins { idea java @@ -7,13 +6,8 @@ plugins { id("com.github.johnrengelman.shadow") version "7.1.2" } -group = "de.torui.coflsky" -val baseGroup: String by project -val mcVersion: String by project -val version: String by project -val mixinGroup = "$baseGroup.mixin" -val modid: String by project -val transformerFile = file("src/main/resources/accesstransformer.cfg") +group = "de.torui.coflmod" +version = "1.6.0" // Toolchains: java { @@ -22,34 +16,13 @@ java { // Minecraft configuration: loom { - log4jConfigs.from(file("log4j2.xml")) launchConfigs { "client" { - // If you don't want mixins, remove these lines - property("mixin.debug", "true") - arg("--tweakClass", "org.spongepowered.asm.launch.MixinTweaker") + arg("--tweakClass", "cc.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker") } } - runConfigs { - "client" { - if (SystemUtils.IS_OS_MAC_OSX) { - // This argument causes a crash on macOS - vmArgs.remove("-XstartOnFirstThread") - } - } - remove(getByName("server")) - } forge { pack200Provider.set(dev.architectury.pack200.java.Pack200Adapter()) - // If you don't want mixins, remove this lines - mixinConfig("mixins.$modid.json") - if (transformerFile.exists()) { - println("Installing access transformer") - accessTransformer(transformerFile) - } - } - mixin { - defaultRefmapName.set("mixins.$modid.refmap.json") } } @@ -64,7 +37,8 @@ repositories { maven("https://repo.spongepowered.org/maven/") // If you don't want to log in with your real minecraft account, remove this line maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") - maven ("https://jitpack.io" ) + // Polyfrost repo for OneConfig + maven("https://repo.polyfrost.org/releases") } val shadowImpl by configurations.creating { @@ -77,19 +51,21 @@ dependencies { forge("net.minecraftforge:forge:1.8.9-11.15.1.2318-1.8.9") annotationProcessor("org.spongepowered:mixin:0.8.4-SNAPSHOT") - //modImplementation(include("org.greenrobot:eventbus-java:3.3.1")) - shadowImpl("org.greenrobot:eventbus-java:3.3.1") - shadowImpl("com.github.Coflnet:coflskycore:5691541e14") + shadowImpl("com.neovisionaries:nv-websocket-client:2.14") + // OneConfig library for legacy Forge 1.8.9 (compile-time only, wrapper will fetch at runtime) + compileOnly("cc.polyfrost:oneconfig-1.8.9-forge:0.2.2-alpha+") + // Shade the LaunchWrapper tweaker inside the mod jar so it runs before Forge + shadowImpl("cc.polyfrost:oneconfig-wrapper-launchwrapper:1.0.0-beta17") - // If you don't want mixins, remove these lines - shadowImpl("org.spongepowered:mixin:0.7.11-SNAPSHOT") { - isTransitive = false - } - annotationProcessor("org.spongepowered:mixin:0.8.5-SNAPSHOT") + // ByteBuddy for runtime class generation + shadowImpl("net.bytebuddy:byte-buddy-dep:1.11.22") // If you don't want to log in with your real minecraft account, remove this line - runtimeOnly("me.djtheredstoner:DevAuth-forge-legacy:1.2.1") + modRuntimeOnly("me.djtheredstoner:DevAuth-forge-legacy:1.2.0") { + exclude(group = "org.jetbrains.kotlin") + exclude(group = "org.jetbrains.kotlinx") + } } @@ -99,32 +75,16 @@ tasks.withType(JavaCompile::class) { options.encoding = "UTF-8" } -tasks.withType(org.gradle.jvm.tasks.Jar::class) { - archiveBaseName.set(modid) +tasks.withType(Jar::class) { + archiveBaseName.set("SkyCofl") manifest.attributes.run { this["FMLCorePluginContainsFMLMod"] = "true" this["ForceLoadAsMod"] = "true" - - // If you don't want mixins, remove these lines - this["TweakClass"] = "org.spongepowered.asm.launch.MixinTweaker" - this["MixinConfigs"] = "mixins.$modid.json" - if (transformerFile.exists()) - this["FMLAT"] = "${modid}_at.cfg" + this["Manifest-Version"] = "1.0" + this["TweakClass"] = "cc.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker" } } -tasks.processResources { - inputs.property("version", project.version) - inputs.property("mcversion", mcVersion) - inputs.property("modid", modid) - inputs.property("basePackage", baseGroup) - - filesMatching(listOf("mcmod.info", "mixins.$modid.json")) { - expand(inputs.properties) - } - - rename("accesstransformer.cfg", "META-INF/${modid}_at.cfg") -} val remapJar by tasks.named("remapJar") { archiveClassifier.set("all") @@ -143,4 +103,3 @@ tasks.shadowJar { } tasks.assemble.get().dependsOn(tasks.remapJar) - diff --git a/gradle.properties b/gradle.properties index 7ae8093..f682d3d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,4 @@ loom.platform=forge org.gradle.jvmargs=-Xmx2g -baseGroup = de.torui.coflsky -mcVersion = 1.8.9 -modid = skycofl -version = 1.7.0 +# Use JDK 17 for Gradle itself (update path if different) +org.gradle.java.home=C:\\Program Files\\Java\\jdk-17 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index db4c326..7c08e4f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 307308d..9d310a8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,8 +18,4 @@ pluginManagement { } } -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version("0.6.0") -} - rootProject.name = "SkyCofl" \ No newline at end of file diff --git a/src/main/java/de/torui/coflsky/CoflSky.java b/src/main/java/de/torui/coflsky/CoflSky.java index 3b9dda0..deb17f3 100644 --- a/src/main/java/de/torui/coflsky/CoflSky.java +++ b/src/main/java/de/torui/coflsky/CoflSky.java @@ -1,41 +1,54 @@ package de.torui.coflsky; - import java.io.File; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Paths; -import CoflCore.CoflCore; import com.google.gson.Gson; -import de.torui.CoflCore.CoflCore.configuration.LocalConfig; -import CoflCore.configuration.GUIType; +import de.torui.coflsky.configuration.LocalConfig; +import de.torui.coflsky.config.CoflConfig; +import de.torui.coflsky.gui.GUIType; import de.torui.coflsky.handlers.EventRegistry; import de.torui.coflsky.listeners.ChatListener; +import de.torui.coflsky.listeners.OneConfigOpenListener; +import de.torui.coflsky.proxy.APIKeyManager; import de.torui.coflsky.gui.tfm.ButtonRemapper; import de.torui.coflsky.gui.tfm.ChatMessageSendHandler; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import org.lwjgl.input.Keyboard; -import CoflCore.network.WSClientWrapper; +import de.torui.coflsky.network.WSClientWrapper; import net.minecraft.client.settings.KeyBinding; import net.minecraftforge.client.ClientCommandHandler; -import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.client.registry.ClientRegistry; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventHandler; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.common.MinecraftForge; @Mod(modid = CoflSky.MODID, version = CoflSky.VERSION) public class CoflSky { public static final String MODID = "CoflSky"; - public static final String VERSION = "1.7.0"; + public static final String VERSION = "1.6.0"; + public static WSClientWrapper Wrapper; public static KeyBinding[] keyBindings; public static EventRegistry Events; public static File configFile; private File coflDir; + public static LocalConfig config; + + public static final String[] webSocketURIPrefix = new String[]{ + "wss://sky.coflnet.com/modsocket", + // fallback for old java versions not supporting new tls certificates + "ws://sky-mod.coflnet.com/modsocket", + }; + + public static String CommandUri = Config.BaseUrl + "/api/mod/commands"; + private final static APIKeyManager apiKeyManager = new APIKeyManager(); + @EventHandler public void preInit(FMLPreInitializationEvent event) { @@ -44,18 +57,35 @@ public void preInit(FMLPreInitializationEvent event) { coflDir = new File(event.getModConfigurationDirectory(), "CoflSky"); coflDir.mkdirs(); configFile = new File(coflDir, "config.json"); - CoflCore cofl = new CoflCore(); - cofl.init(coflDir.getAbsoluteFile().toPath()); - cofl.registerEventFile(new WSCommandHandler()); + try { + if (configFile.isFile()) { + configString = new String(Files.readAllBytes(Paths.get(configFile.getPath()))); + config = gson.fromJson(configString, LocalConfig.class); + } + } catch (Exception e) { + e.printStackTrace(); + } + if (config == null) { + config = LocalConfig.createDefaultConfig(); + } - MinecraftForge.EVENT_BUS.register(new ChatListener()); + try { + this.apiKeyManager.loadIfExists(); + } catch (Exception exception) { + exception.printStackTrace(); + } + MinecraftForge.EVENT_BUS.register(new ChatListener()); + // Initialise OneConfig configuration (GUI will open via keybind automatically) + CoflConfig oneConfig = new CoflConfig(); // Cache all the mods on load WSCommandHandler.cacheMods(); } @EventHandler public void init(FMLInitializationEvent event) { + CoflSky.Wrapper = new WSClientWrapper(webSocketURIPrefix); + keyBindings = new KeyBinding[]{ new KeyBinding("key.replay_last.onclick", Keyboard.KEY_NONE, "SkyCofl"), new KeyBinding("key.start_highest_bid", Keyboard.KEY_NONE, "SkyCofl"), @@ -66,6 +96,8 @@ public void init(FMLInitializationEvent event) { ClientCommandHandler.instance.registerCommand(new CoflSkyCommand()); ClientCommandHandler.instance.registerCommand(new ColfCommand()); ClientCommandHandler.instance.registerCommand(new FlipperChatCommand()); + // register OneConfig command + ClientCommandHandler.instance.registerCommand(new de.torui.coflsky.commands.CoflGuiCommand()); for (int i = 0; i < keyBindings.length; ++i) { ClientRegistry.registerKeyBinding(keyBindings[i]); @@ -73,10 +105,24 @@ public void init(FMLInitializationEvent event) { } Events = new EventRegistry(); MinecraftForge.EVENT_BUS.register(Events); - if (CoflCore.config.purchaseOverlay == GUIType.TFM) { + if (config.purchaseOverlay == GUIType.TFM) { MinecraftForge.EVENT_BUS.register(ButtonRemapper.getInstance()); } MinecraftForge.EVENT_BUS.register(new ChatMessageSendHandler()); + // GUI open listener for OneConfig to fetch server settings + MinecraftForge.EVENT_BUS.register(new OneConfigOpenListener()); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + config.saveConfig(configFile, config); + try { + apiKeyManager.saveKey(); + } catch (Exception exception) { + exception.printStackTrace(); + } + })); + } + + public static APIKeyManager getAPIKeyManager() { + return apiKeyManager; } + } - diff --git a/src/main/java/de/torui/coflsky/CoflSkyCommand.java b/src/main/java/de/torui/coflsky/CoflSkyCommand.java index 142264d..635d5a0 100644 --- a/src/main/java/de/torui/coflsky/CoflSkyCommand.java +++ b/src/main/java/de/torui/coflsky/CoflSkyCommand.java @@ -3,17 +3,17 @@ import java.io.IOException; import java.util.*; -import CoflCore.commands.Command; -import CoflCore.commands.CommandType; -import CoflCore.commands.JsonStringCommand; -import CoflCore.commands.RawCommand; -import CoflCore.commands.models.FlipData; -import CoflCore.configuration.GUIType; +import de.torui.coflsky.commands.Command; +import de.torui.coflsky.commands.CommandType; +import de.torui.coflsky.commands.JsonStringCommand; +import de.torui.coflsky.commands.RawCommand; +import de.torui.coflsky.commands.models.FlipData; +import de.torui.coflsky.gui.GUIType; import de.torui.coflsky.gui.bingui.BinGuiManager; import de.torui.coflsky.gui.tfm.ButtonRemapper; -import CoflCore.misc.SessionManager; -import CoflCore.misc.SessionManager.CoflSession; -import CoflCore.network.WSClient; +import de.torui.coflsky.minecraft_integration.CoflSessionManager; +import de.torui.coflsky.minecraft_integration.CoflSessionManager.CoflSession; +import de.torui.coflsky.network.WSClient; import de.torui.coflsky.minecraft_integration.PlayerDataProvider; import net.minecraft.client.Minecraft; import net.minecraft.command.CommandBase; @@ -73,7 +73,7 @@ public List addTabCompletionOptions(ICommandSender sender, String[] args } if (args.length == 2 && args[0].equalsIgnoreCase("set")) { List options = Arrays.asList("lbin", "finders", "onlyBin", "whitelistAftermain", "DisableFlips", - "DebugMode", "blockHighCompetition", "minProfit", "minProfitPercent", "minVolume", "maxCost", + "DebugMode", "BlockHighCompetitionFlips", "minProfit", "minProfitPercent", "minVolume", "maxCost", "modjustProfit", "modsoundOnFlip", "modshortNumbers", "modshortNames", "modblockTenSecMsg", "modformat", "modblockedFormat", "modchat", "modcountdown", "modhideNoBestFlip", "modtimerX", "modtimerY", "modtimerSeconds", "modtimerScale", "modtimerPrefix", "modtimerPrecision", @@ -96,6 +96,49 @@ public void processCommand(ICommandSender sender, String[] args) throws CommandE if (args.length >= 1) { switch (args[0].toLowerCase()) { + case "start": + // todo: start + // possible workaround for https://github.com/Coflnet/SkyblockMod/issues/48 + CoflSky.Wrapper.stop(); + sender.addChatMessage(new ChatComponentText("starting SkyCofl connection...")); + CoflSky.Wrapper.startConnection(); + break; + case "stop": + CoflSky.Wrapper.stop(); + sender.addChatMessage(new ChatComponentText("you stopped the connection to ") + .appendSibling(new ChatComponentText("C") + .setChatStyle(new ChatStyle().setColor(EnumChatFormatting.DARK_BLUE))) + .appendSibling(new ChatComponentText("oflnet") + .setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD))) + .appendSibling(new ChatComponentText(".\n To reconnect enter ")) + .appendSibling(new ChatComponentText("\"") + .setChatStyle(new ChatStyle().setColor(EnumChatFormatting.AQUA))) + .appendSibling(new ChatComponentText("/cofl start")) + .appendSibling(new ChatComponentText("\"") + .setChatStyle(new ChatStyle().setColor(EnumChatFormatting.AQUA))) + .appendSibling(new ChatComponentText(" or click this message")) + .setChatStyle(new ChatStyle() + .setChatClickEvent(new ClickEvent(Action.RUN_COMMAND, "/cofl start")))); + break; + case "callback": + CallbackCommand(args); + break; + case "dev": + if (Config.BaseUrl.contains("localhost")) { + CoflSky.Wrapper.startConnection(); + Config.BaseUrl = "https://sky.coflnet.com"; + } else { + CoflSky.Wrapper.initializeNewSocket("ws://localhost:8009/modsocket"); + Config.BaseUrl = "http://localhost:5005"; + } + sender.addChatMessage(new ChatComponentText("toggled dev mode, now using " + Config.BaseUrl)); + break; + case "status": + sender.addChatMessage(new ChatComponentText(StatusMessage())); + break; + case "reset": + HandleReset(); + break; case "copytoclipboard": String textForClipboard = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); try { @@ -108,9 +151,56 @@ public void processCommand(ICommandSender sender, String[] args) throws CommandE Minecraft.getMinecraft().thePlayer .addChatMessage(new ChatComponentText("Failed to copy text to clipboard!")); } - CoflCore.CoflCore.Wrapper.SendMessage(new JsonStringCommand(CommandType.Clicked, + CoflSky.Wrapper.SendMessage(new JsonStringCommand(CommandType.Clicked, WSClient.gson.toJson("copy:" + textForClipboard))); break; + case "connect": + if (args.length == 2) { + String destination = args[1]; + + if (!destination.contains("://")) { + destination = new String(Base64.getDecoder().decode(destination)); + } + sender.addChatMessage(new ChatComponentText("Stopping connection!")); + CoflSky.Wrapper.stop(); + sender.addChatMessage(new ChatComponentText("Opening connection to " + destination)); + if (CoflSky.Wrapper.initializeNewSocket(destination)) { + sender.addChatMessage(new ChatComponentText( + "SkyCofl server is reachable, waiting for connection to be established")); + } else { + sender.addChatMessage(new ChatComponentText( + "Could not open connection, please check the logs and report them on your Discord!")); + } + } else { + sender.addChatMessage(new ChatComponentText("§cPleace specify a server to connect to")); + } + break; + case "openauctiongui": + FlipData flip = WSCommandHandler.flipHandler.fds.getFlipById(args[1]); + boolean shouldInvalidate = args.length >= 3 && args[2].equals("true"); + + // Is not a stored flip -> just open the auction + if (flip == null) { + WSCommandHandler.flipHandler.lastClickedFlipMessage = ""; + Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewauction " + args[1]); + return; + } + + String oneLineMessage = String.join(" ", flip.getMessageAsString()).replaceAll("\n", "") + .split(",§7 sellers ah")[0]; + + if (shouldInvalidate) { + WSCommandHandler.flipHandler.fds.InvalidateFlip(flip); + } + + WSCommandHandler.flipHandler.lastClickedFlipMessage = oneLineMessage; + + Minecraft.getMinecraft().addScheduledTask(() -> { + BinGuiManager.openNewFlipGui(oneLineMessage, flip.Render); + }); + + Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewauction " + flip.Id); + break; case "setgui": if (args.length != 2) { sender.addChatMessage(new ChatComponentText("[§1C§6oflnet§f]§7: §7Available GUIs:")); @@ -121,26 +211,26 @@ public void processCommand(ICommandSender sender, String[] args) throws CommandE } if (args[1].equalsIgnoreCase("cofl")) { - CoflCore.CoflCore.config.purchaseOverlay = GUIType.COFL; + CoflSky.config.purchaseOverlay = GUIType.COFL; sender.addChatMessage( new ChatComponentText("[§1C§6oflnet§f]§7: §7Set §bPurchase Overlay §7to: §fCofl")); MinecraftForge.EVENT_BUS.unregister(ButtonRemapper.getInstance()); } if (args[1].equalsIgnoreCase("tfm")) { - CoflCore.CoflCore.config.purchaseOverlay = GUIType.TFM; + CoflSky.config.purchaseOverlay = GUIType.TFM; sender.addChatMessage( new ChatComponentText("[§1C§6oflnet§f]§7: §7Set §bPurchase Overlay §7to: §fTFM")); MinecraftForge.EVENT_BUS.register(ButtonRemapper.getInstance()); } if (args[1].equalsIgnoreCase("off") || args[1].equalsIgnoreCase("false")) { - CoflCore.CoflCore.config.purchaseOverlay = null; + CoflSky.config.purchaseOverlay = null; sender.addChatMessage( new ChatComponentText("[§1C§6oflnet§f]§7: §7Set §bPurchase Overlay §7to: §fOff")); MinecraftForge.EVENT_BUS.unregister(ButtonRemapper.getInstance()); } break; default: - CoflCore.CoflSkyCommand.processCommand(args, PlayerDataProvider.getUsername()); + SendCommandToServer(args, sender); } } else { SendCommandToServer("help", "general", sender); @@ -148,7 +238,40 @@ public void processCommand(ICommandSender sender, String[] args) throws CommandE }).start(); } + private void HandleReset() { + CoflSky.Wrapper.SendMessage(new Command(CommandType.Reset, "")); + CoflSky.Wrapper.stop(); + Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Stopping Connection to SkyCofl")); + CoflSessionManager.DeleteAllCoflSessions(); + Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Deleting SkyCofl sessions...")); + if (CoflSky.Wrapper.startConnection()) + Minecraft.getMinecraft().thePlayer + .addChatMessage(new ChatComponentText("Started the Connection to SkyCofl")); + } + + public String StatusMessage() { + + String vendor = System.getProperty("java.vm.vendor"); + String name = System.getProperty("java.vm.name"); + String version = System.getProperty("java.version"); + String detailedVersion = System.getProperty("java.vm.version"); + + String status = vendor + " " + name + " " + version + " " + detailedVersion + "|Connection = " + + (CoflSky.Wrapper != null ? CoflSky.Wrapper.GetStatus() : "UNINITIALIZED_WRAPPER"); + try { + status += " uri=" + CoflSky.Wrapper.socket.uri.toString(); + } catch (NullPointerException npe) { + } + + try { + CoflSession session = CoflSessionManager.GetCoflSession(PlayerDataProvider.getUsername()); + String sessionString = CoflSessionManager.gson.toJson(session); + status += " session=" + sessionString; + } catch (IOException e) { + } + return status; + } public void SendCommandToServer(String[] args, ICommandSender sender) { String command = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); @@ -157,8 +280,8 @@ public void SendCommandToServer(String[] args, ICommandSender sender) { public void SendCommandToServer(String command, String arguments, ICommandSender sender) { RawCommand rc = new RawCommand(command, WSClient.gson.toJson(arguments)); - if (CoflCore.CoflCore.Wrapper.isRunning) { - CoflCore.CoflCore.Wrapper.SendMessage(rc); + if (CoflSky.Wrapper.isRunning) { + CoflSky.Wrapper.SendMessage(rc); } else { SendAfterStart(sender, rc); } @@ -168,7 +291,21 @@ private static synchronized void SendAfterStart(ICommandSender sender, RawComman sender.addChatMessage(new ChatComponentText("CoflSky wasn't active.") .setChatStyle(new ChatStyle().setColor(EnumChatFormatting.RED))); // CoflSky.Wrapper.stop(); - CoflCore.CoflCore.Wrapper.startConnection(PlayerDataProvider.getUsername()); - CoflCore.CoflCore.Wrapper.SendMessage(rc); + CoflSky.Wrapper.startConnection(); + CoflSky.Wrapper.SendMessage(rc); + } + + public void CallbackCommand(String[] args) { + + String command = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + System.out.println("CallbackData: " + command); + // new Thread(()->{ + System.out.println("Callback: " + command); + WSCommandHandler.HandleCommand(new JsonStringCommand(CommandType.Execute, WSClient.gson.toJson(command)), + Minecraft.getMinecraft().thePlayer); + CoflSky.Wrapper.SendMessage(new JsonStringCommand(CommandType.Clicked, WSClient.gson.toJson(command))); + + System.out.println("Sent!"); + // }).start(); } } diff --git a/src/main/java/de/torui/coflsky/FlipHandler.java b/src/main/java/de/torui/coflsky/FlipHandler.java new file mode 100644 index 0000000..2b59c7f --- /dev/null +++ b/src/main/java/de/torui/coflsky/FlipHandler.java @@ -0,0 +1,126 @@ +package de.torui.coflsky; + +import de.torui.coflsky.commands.models.FlipData; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class FlipHandler { + + public static class FlipDataStructure { + + private Map Flips = new ConcurrentHashMap<>(); + private Map ReverseMap = new ConcurrentHashMap<>(); + + private FlipData HighestFlip = null; + private FlipData LastFlip = null; + + private Timer t = new Timer(); + private TimerTask CurrentTask = null; + + public synchronized void RunHouseKeeping() { + synchronized (Flips) { + + Long RemoveAllPrior = System.currentTimeMillis() - (Config.KeepFlipsForSeconds * 1000); + Flips.keySet().stream().filter(l -> l <= RemoveAllPrior).forEach(l -> RemoveLong(l)); + if (!Flips.isEmpty()) { + HighestFlip = Flips.values().stream().max((f1, f2) -> f1.Worth - f2.Worth).orElse(null); + } else { + HighestFlip = null; + } + } + + if (CurrentTask != null) { + CurrentTask.cancel(); + CurrentTask = null; + t.purge(); + } + if (!Flips.isEmpty()) { + CurrentTask = new TimerTask() { + @Override + public void run() { + RunHouseKeeping(); + } + }; + t.schedule(CurrentTask, Config.KeepFlipsForSeconds * 1000 + /* small arbitrary delay */150); + } + } + + public synchronized void Insert(FlipData flip) { + Long l = System.currentTimeMillis(); + LastFlip = flip; + + synchronized (Flips) { + Flips.put(l, flip); + ReverseMap.put(flip, l); + } + + RunHouseKeeping(); + } + + private void RemoveLong(Long l) { + if (l == null) + return; + synchronized (Flips) { + FlipData f = Flips.get(l); + if (f != null) { + ReverseMap.remove(f); + Flips.remove(l); + } + } + } + + private void RemoveFlip(FlipData f) { + if (f == null) + return; + + synchronized (Flips) { + Long l = ReverseMap.get(f); + if (l != null) { + Flips.remove(l); + ReverseMap.remove(f); + } + } + } + + public FlipData GetHighestFlip() { + return HighestFlip; + } + + public FlipData GetLastFlip() { + if (LastFlip == null) { + return null; + } + Long l = ReverseMap.get(LastFlip); + if (l == null) { + LastFlip = null; + } + return LastFlip; + } + + public FlipData getFlipById(String id) { + FlipData[] flips = Flips.values().stream().filter(flipData -> flipData.Id.equals(id)).toArray(FlipData[]::new); + if (flips.length == 0) { + return null; + } + return flips[0]; + } + + public void InvalidateFlip(FlipData flip) { + RemoveFlip(flip); + RunHouseKeeping(); + } + + public int CurrentFlips() { + return Flips.size(); + } + + } + + public FlipDataStructure fds; + public String lastClickedFlipMessage; + + public FlipHandler() { + fds = new FlipDataStructure(); + } + +} diff --git a/src/main/java/de/torui/coflsky/WSCommandHandler.java b/src/main/java/de/torui/coflsky/WSCommandHandler.java index af10dac..839bee1 100644 --- a/src/main/java/de/torui/coflsky/WSCommandHandler.java +++ b/src/main/java/de/torui/coflsky/WSCommandHandler.java @@ -1,16 +1,19 @@ package de.torui.coflsky; -import CoflCore.events.*; import com.google.gson.Gson; - -import CoflCore.commands.Command; -import CoflCore.commands.RawCommand; -import CoflCore.commands.models.*; -import de.torui.coflsky.gui.bingui.BinGuiManager; -import de.torui.coflsky.handlers.ForgeDescriptionHandler; +import com.google.gson.reflect.TypeToken; + +import de.torui.coflsky.commands.Command; +import de.torui.coflsky.commands.CommandType; +import de.torui.coflsky.commands.JsonStringCommand; +import de.torui.coflsky.commands.RawCommand; +import de.torui.coflsky.commands.models.*; +import de.torui.coflsky.configuration.ConfigurationManager; import de.torui.coflsky.handlers.EventRegistry; -import CoflCore.proxy.ProxyManager; +import de.torui.coflsky.handlers.EventHandler; +import de.torui.coflsky.proxy.ProxyManager; import de.torui.coflsky.utils.FileUtils; +import de.torui.coflsky.commands.models.TimerData; import net.minecraft.client.Minecraft; import net.minecraft.client.audio.PositionedSoundRecord; import net.minecraft.client.audio.SoundHandler; @@ -25,8 +28,6 @@ import net.minecraftforge.client.ClientCommandHandler; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.ModContainer; -import org.greenrobot.eventbus.Subscribe; -import CoflCore.CoflCore; import java.io.File; @@ -36,99 +37,88 @@ public class WSCommandHandler { public static transient String lastOnClickEvent; + public static FlipHandler flipHandler = new FlipHandler(); private static final ModListData modListData = new ModListData(); private static final Gson gson = new Gson(); private static final ProxyManager proxyManager = new ProxyManager(); - public static int[][] highlightCoordinates = new int[0][]; - @Subscribe - public void onReceiveCommand(ReceiveCommand event){ - // called on every command + public static boolean HandleCommand(JsonStringCommand cmd, Entity sender) { + String commandString = cmd.toString(); + System.out.println("Handling Command=" + commandString); + if (commandString.startsWith("Command [Type=null")) { + // Attempt to detect settings blob (array of objects with key/name/value) and apply directly + if (cmd.getData() != null && cmd.getData().trim().startsWith("[") && cmd.getData().contains("\"key\"")) { + // Probably settings json coming via websocket + de.torui.coflsky.util.ServerSettingsLoader.applySettings(cmd.getData()); + return true; + } + return false; // unknown command + } + + switch (cmd.getType()) { + case WriteToChat: + WriteToChat(cmd.GetAs(new TypeToken() { + })); + break; + case Execute: + Execute(cmd.GetAs(new TypeToken() { + }), sender); + break; + case PlaySound: + SoundData sc = cmd.GetAs(new TypeToken() { + }).getData(); + PlaySound(sc.Name, sc.Pitch); + break; + case ChatMessage: + ChatMessage(cmd.GetAs(new TypeToken() { + })); + break; + case Flip: + Flip(cmd.GetAs(new TypeToken() { + })); + break; + case PrivacySettings: + new ConfigurationManager().UpdateConfiguration(cmd.getData()); + EventHandler.UploadScoreboardData(); // on startup + break; + case Countdown: + StartTimer(cmd.GetAs(new TypeToken() { + })); + break; + case GetMods: + getMods(); + case GetScoreboard: + EventHandler.UploadScoreboardData(); + break; + case RegisterKeybind: + EventRegistry.AddHotKeys(cmd.GetAs(new TypeToken() { + }).getData()); + break; + case ProxyRequest: + handleProxyRequest(cmd.GetAs(new TypeToken() { + }).getData()); + break; + default: + break; + } + + return true; } - @Subscribe - public void onFlipReceive(OnFlipReceive event){ + private static void Flip(Command cmd) { // handle chat message - - ChatMessageData[] messages = event.FlipData.Messages; - SoundData sound = event.FlipData.Sound; + ChatMessageData[] messages = cmd.getData().Messages; + SoundData sound = cmd.getData().Sound; if (sound != null && sound.Name != null) { PlaySound(sound.Name, sound.Pitch); } - ChatMessage(messages); - CoflCore.flipHandler.fds.Insert(event.FlipData); + Command showCmd = new Command(CommandType.ChatMessage, messages); + ChatMessage(showCmd); + flipHandler.fds.Insert(cmd.getData()); // trigger the onAfterHotkeyPressed function to open the flip if the correct // hotkey is currently still pressed EventRegistry.onAfterKeyPressed(); } - @Subscribe - public void onChatMessage(OnChatMessageReceive event){ - ChatMessage(event.ChatMessages); - } - - @Subscribe - public void onModChatMessage(OnModChatMessage event){ - ChatMessage(new ChatMessageData[]{ - new ChatMessageData(event.message, null,null) - }); - } - - @Subscribe - public void onChatMessageDataReceive(OnWriteToChatReceive event){ - ChatMessage(new ChatMessageData[]{ - event.ChatMessage - }); - } - - @Subscribe - public void onPlaySoundReceive(OnPlaySoundReceive event){ - if(event.Sound == null || event.Sound.getSoundName() == null) return; - - String soundName = event.Sound.getSoundName(); - float pitch = event.Sound.getSoundPitch(); - PlaySound(soundName, pitch); - } - - @Subscribe - public void onCountdownReceive(OnCountdownReceive event){ - de.torui.coflsky.minecraft_integration.CountdownTimer.startCountdown(event.CountdownData); - } - - @Subscribe - public void onOpenAuctionGUI(OnOpenAuctionGUI event){ - BinGuiManager.openNewFlipGui(event.flip.getMessageAsString().replaceAll("\n", "") - .split(",§7 sellers ah")[0], event.flip.Render); - Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewauction " + event.flip.Id); - } - - @Subscribe - public void onExecuteCommand(OnExecuteCommand event){ - Execute(event.Command, Minecraft.getMinecraft().thePlayer); - } - @Subscribe - public void onCloseGUI(OnCloseGUI event){ - // close the current open gui if any - Minecraft mc = Minecraft.getMinecraft(); - if (mc.currentScreen != null) { - mc.displayGuiScreen(null); - } - } - - @Subscribe - public void onGetInventory(OnGetInventory event) { - ForgeDescriptionHandler.uploadInventory(); - } - @Subscribe - public void onHighlightBlocks(OnHighlightBlocks event) { - highlightCoordinates = new int[event.positions.size()][]; - for (int i = 0; i < event.positions.size(); i++) { - highlightCoordinates[i] = new int[]{ - event.positions.get(i).getX(), - event.positions.get(i).getY(), - event.positions.get(i).getZ() - }; - } - } private static void handleProxyRequest(ProxyRequest[] request) { for (ProxyRequest req : request) { @@ -158,7 +148,7 @@ public static void cacheMods() { private static void getMods() { // the Cofl server has asked for an mod list now let's respond with all the info - CoflCore.Wrapper.SendMessage(new RawCommand("foundMods", gson.toJson(modListData))); + CoflSky.Wrapper.SendMessage(new RawCommand("foundMods", gson.toJson(modListData))); } private static void PlaySound(String soundName, float pitch) { @@ -177,6 +167,12 @@ private static void Execute(Command cmd, Entity sender) { Execute(cmd.getData(), sender); } + /** + * Starts a countdown + */ + private static void StartTimer(Command cmd) { + de.torui.coflsky.minecraft_integration.CountdownTimer.startCountdown(cmd.getData()); + } public static void Execute(String cmd, Entity sender) { if (cmd.startsWith("/cofl") || cmd.startsWith("http")) { @@ -229,11 +225,13 @@ public static void sendChatMessage(IChatComponent message) { Minecraft.getMinecraft().thePlayer.addChatMessage(message); } - public static IChatComponent ChatMessage(ChatMessageData[] array) { + public static IChatComponent ChatMessage(Command cmd) { + ChatMessageData[] list = cmd.getData(); + IChatComponent master = new ChatComponentText(""); - String fullMessage = ChatMessageDataToString(array); + String fullMessage = ChatMessageDataToString(list); - for (ChatMessageData wcmd : array) { + for (ChatMessageData wcmd : list) { IChatComponent comp = CommandToChatComponent(wcmd, fullMessage); if (comp != null) master.appendSibling(comp); diff --git a/src/main/java/de/torui/coflsky/commands/CoflGuiCommand.java b/src/main/java/de/torui/coflsky/commands/CoflGuiCommand.java new file mode 100644 index 0000000..075f35b --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/CoflGuiCommand.java @@ -0,0 +1,61 @@ +package de.torui.coflsky.commands; + +import cc.polyfrost.oneconfig.utils.commands.annotations.Command; +import cc.polyfrost.oneconfig.utils.commands.annotations.Main; +import cc.polyfrost.oneconfig.libs.universal.UChat; +import de.torui.coflsky.config.CoflConfig; +import de.torui.coflsky.util.ServerSettingsLoader; + +import java.util.Arrays; +import java.util.List; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.util.BlockPos; + +@Command(value = "coflgui", aliases = {"cg"}, description = "Open the Cofl settings GUI.") +public class CoflGuiCommand extends CommandBase { + + @Main + private void handle() { + // If a load is already underway, inform user and avoid duplicate trigger + if (ServerSettingsLoader.isRunning()) { + UChat.chat("&7[Cofl] &eSettings are already loading..."); + return; + } + // Fetch latest settings every time; open GUI after load. + ServerSettingsLoader.requestSettingsAndThen(CoflConfig::openGuiStatic); + } + + @Override + public String getCommandName() { + return "coflgui"; + } + + @Override + public List getCommandAliases() { + return Arrays.asList("cg"); + } + + @Override + public String getCommandUsage(ICommandSender sender) { + return "/coflgui - Open the Cofl settings GUI"; + } + + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + handle(); + } + + @Override + public int getRequiredPermissionLevel() { + return 0; + } + + // For 1.8.9, override to support tab completion (none needed) + @Override + public List addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) { + return null; + } +} diff --git a/src/main/java/de/torui/coflsky/commands/Command.java b/src/main/java/de/torui/coflsky/commands/Command.java index 59fac3f..20ab8f1 100644 --- a/src/main/java/de/torui/coflsky/commands/Command.java +++ b/src/main/java/de/torui/coflsky/commands/Command.java @@ -1,6 +1,5 @@ package de.torui.coflsky.commands; -import CoflCore.commands.CommandType; import com.google.gson.annotations.SerializedName; public class Command { diff --git a/src/main/java/de/torui/coflsky/commands/CommandType.java b/src/main/java/de/torui/coflsky/commands/CommandType.java new file mode 100644 index 0000000..e75cc27 --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/CommandType.java @@ -0,0 +1,94 @@ +package de.torui.coflsky.commands; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public enum CommandType { + @SerializedName("writeToChat") + WriteToChat, + @SerializedName("set") + set, + + @SerializedName("execute") + Execute, + + @SerializedName("tokenLogin") + TokenLogin, + + @SerializedName("clicked") + Clicked, + + @SerializedName("playSound") + PlaySound, + + @SerializedName("chatMessage") + ChatMessage, + + @SerializedName("purchaseStart") + PurchaseStart, + + @SerializedName("purchaseConfirm") + PurchaseConfirm, + + @SerializedName("reset") + Reset, + @SerializedName("flip") + Flip, + @SerializedName("privacySettings") + PrivacySettings, + @SerializedName("countdown") + Countdown, + @SerializedName("updatePurse") + updatePurse, + @SerializedName("updateBits") + updateBits, + @SerializedName("updateServer") + updateServer, + @SerializedName("updateLocation") + updateLocation, + @SerializedName("chatBatch") + chatBatch, + @SerializedName("uploadTab") + uploadTab, + @SerializedName("uploadSettings") + uploadSettings, + @SerializedName("uploadScoreboard") + uploadScoreboard, + @SerializedName("getMods") + GetMods, + @SerializedName("proxy") + ProxyRequest, + @SerializedName("registerKeybind") + RegisterKeybind, + @SerializedName("getScoreboard") + GetScoreboard; + + + public static Map data; + static { + data = new HashMap<>(); + for(CommandType ct : CommandType.values()) { + try { + Field f = CommandType.class.getField(ct.name()); + + if(f.isAnnotationPresent(SerializedName.class)) { + SerializedName sn = f.getAnnotation(SerializedName.class); + data.put(ct, sn.value()); + } else { + throw new RuntimeException("Commandtype must have SerializeName Annotation!"); + } + + } catch (NoSuchFieldException | SecurityException e) { + System.err.println("This should never occur!"); + e.printStackTrace(); + } + } + } + + public String ToJson() { + return data.get(this); + } +} diff --git a/src/main/java/de/torui/coflsky/commands/JsonStringCommand.java b/src/main/java/de/torui/coflsky/commands/JsonStringCommand.java new file mode 100644 index 0000000..a4ee15e --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/JsonStringCommand.java @@ -0,0 +1,30 @@ +package de.torui.coflsky.commands; + +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import de.torui.coflsky.network.WSClient; + +public class JsonStringCommand extends Command { + + public JsonStringCommand(String type, String data) { + this.setType(WSClient.gson.fromJson(type, CommandType.class)); + this.setData(data); + } + + public JsonStringCommand() { + super(); + + } + + public JsonStringCommand(CommandType type, String data) { + super(type, data); + } + + public Command GetAs(TypeToken type){ + T t = new GsonBuilder().create().fromJson(this.getData(),type.getType()); + Command cmd = new Command(this.getType(), t); + + return (Command) cmd; + } +} diff --git a/src/main/java/de/torui/coflsky/commands/RawCommand.java b/src/main/java/de/torui/coflsky/commands/RawCommand.java new file mode 100644 index 0000000..f2054e8 --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/RawCommand.java @@ -0,0 +1,33 @@ +package de.torui.coflsky.commands; + +import com.google.gson.annotations.SerializedName; + +public class RawCommand { + @SerializedName("type") + private String Type; + + @SerializedName("data") + private String Data; + + public RawCommand(String type, String data) { + this.Type = type; + this.Data=data; + } + + public String getType() { + return Type; + } + + public void setType(String type) { + Type = type; + } + + public String getData() { + return Data; + } + + public void setData(String data) { + Data = data; + } + +} diff --git a/src/main/java/de/torui/coflsky/commands/models/AuctionData.java b/src/main/java/de/torui/coflsky/commands/models/AuctionData.java new file mode 100644 index 0000000..ff1f705 --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/models/AuctionData.java @@ -0,0 +1,30 @@ +package de.torui.coflsky.commands.models; + +import com.google.gson.annotations.SerializedName; + +public class AuctionData { + @SerializedName("auctionId") + private String auctionId; + @SerializedName("itemId") + private String itemId; + public String getAuctionId() { + return auctionId; + } + public void setAuctionId(String auctionId) { + this.auctionId = auctionId; + } + public String getItemId() { + return itemId; + } + public void setItemId(String itemId) { + this.itemId = itemId; + } + public AuctionData(String auctionId, String itemId) { + super(); + this.auctionId = auctionId; + this.itemId = itemId; + } + + public AuctionData() {} + +} diff --git a/src/main/java/de/torui/coflsky/commands/models/ChatMessageData.java b/src/main/java/de/torui/coflsky/commands/models/ChatMessageData.java new file mode 100644 index 0000000..65459c5 --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/models/ChatMessageData.java @@ -0,0 +1,25 @@ +package de.torui.coflsky.commands.models; + +import com.google.gson.annotations.SerializedName; + +public class ChatMessageData { + @SerializedName("text") + public String Text; + @SerializedName("onClick") + public String OnClick; + + @SerializedName("hover") + public String Hover; + + public ChatMessageData() { + + } + + public ChatMessageData(String text, String onClick, String hover) { + super(); + Text = text; + OnClick = onClick; + Hover = hover; + } + +} diff --git a/src/main/java/de/torui/coflsky/commands/models/FlipData.java b/src/main/java/de/torui/coflsky/commands/models/FlipData.java new file mode 100644 index 0000000..0fb9357 --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/models/FlipData.java @@ -0,0 +1,34 @@ +package de.torui.coflsky.commands.models; + +import com.google.gson.annotations.SerializedName; +import de.torui.coflsky.WSCommandHandler; + +public class FlipData { + + @SerializedName("messages") + public ChatMessageData[] Messages; + @SerializedName("id") + public String Id; + @SerializedName("worth") + public int Worth; + @SerializedName("sound") + public SoundData Sound; + @SerializedName("render") + public String Render; + + public FlipData() { + } + + public FlipData(ChatMessageData[] messages, String id, int worth, SoundData sound, String render) { + super(); + Messages = messages; + Id = id; + Worth = worth; + Sound = sound; + Render = render; + } + + public String getMessageAsString(){ + return WSCommandHandler.ChatMessageDataToString(this.Messages); + } +} diff --git a/src/main/java/de/torui/coflsky/commands/models/HotkeyRegister.java b/src/main/java/de/torui/coflsky/commands/models/HotkeyRegister.java new file mode 100644 index 0000000..039a417 --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/models/HotkeyRegister.java @@ -0,0 +1,14 @@ +package de.torui.coflsky.commands.models; + +import com.google.gson.annotations.SerializedName; +import de.torui.coflsky.WSCommandHandler; + +public class HotkeyRegister { + @SerializedName("name") + public String Name; + @SerializedName("defaultKey") + public String DefaultKey; + + public HotkeyRegister() { + } +} diff --git a/src/main/java/de/torui/coflsky/commands/models/ModListData.java b/src/main/java/de/torui/coflsky/commands/models/ModListData.java new file mode 100644 index 0000000..f654ecd --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/models/ModListData.java @@ -0,0 +1,33 @@ +package de.torui.coflsky.commands.models; + +import com.google.gson.annotations.SerializedName; + +import java.util.LinkedList; +import java.util.List; + +public class ModListData { + + @SerializedName("fileNames") + private final List fileNames = new LinkedList<>(); + + @SerializedName("modNames") + private final List modNames = new LinkedList<>(); + + @SerializedName("fileHashes") + private final List fileHashes = new LinkedList<>(); + + + public void addFilename(String name){ + this.fileNames.add(name); + } + + public void addModname(String modname){ + this.modNames.add(modname); + } + + public void addFileHashes(String hash){ + this.fileHashes.add(hash); + } + + +} diff --git a/src/main/java/de/torui/coflsky/commands/models/ProxyRequest.java b/src/main/java/de/torui/coflsky/commands/models/ProxyRequest.java new file mode 100644 index 0000000..9d32f68 --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/models/ProxyRequest.java @@ -0,0 +1,31 @@ +package de.torui.coflsky.commands.models; + +import com.google.gson.annotations.SerializedName; + +public class ProxyRequest { + @SerializedName("uploadTo") + private String uploadTo; + + @SerializedName("id") + private String id; + + @SerializedName("url") + private String url; + + + public String getId(){ + return id; + } + + public String getUrl(){ + return url; + } + + public boolean isUploadEnabled() { + return this.uploadTo != null; + } + + public String getUploadTo() { + return this.uploadTo; + } +} diff --git a/src/main/java/de/torui/coflsky/commands/models/SoundData.java b/src/main/java/de/torui/coflsky/commands/models/SoundData.java new file mode 100644 index 0000000..5df18d3 --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/models/SoundData.java @@ -0,0 +1,23 @@ +package de.torui.coflsky.commands.models; + +import com.google.gson.annotations.SerializedName; + +public class SoundData { + @SerializedName("name") + public String Name; + + @SerializedName("pitch") + public float Pitch; + + public SoundData() { + super(); + } + + public SoundData(String name, float pitch) { + super(); + Name = name; + Pitch = pitch; + } + + +} diff --git a/src/main/java/de/torui/coflsky/commands/models/TimerData.java b/src/main/java/de/torui/coflsky/commands/models/TimerData.java new file mode 100644 index 0000000..50b3d68 --- /dev/null +++ b/src/main/java/de/torui/coflsky/commands/models/TimerData.java @@ -0,0 +1,22 @@ +package de.torui.coflsky.commands.models; + +import com.google.gson.annotations.SerializedName; + +public class TimerData { + + @SerializedName("seconds") + public double seconds; + + @SerializedName("heightPercent") + public int height; + @SerializedName("widthPercent") + public int width; + @SerializedName("scale") + public double scale; + + @SerializedName("prefix") + public String prefix; + + @SerializedName("maxPrecision") + public int maxPrecision; +} diff --git a/src/main/java/de/torui/coflsky/config/CoflConfig.java b/src/main/java/de/torui/coflsky/config/CoflConfig.java new file mode 100644 index 0000000..1253ec9 --- /dev/null +++ b/src/main/java/de/torui/coflsky/config/CoflConfig.java @@ -0,0 +1,436 @@ +package de.torui.coflsky.config; + +import cc.polyfrost.oneconfig.config.Config; +import cc.polyfrost.oneconfig.config.annotations.*; +import cc.polyfrost.oneconfig.config.data.*; +import de.torui.coflsky.CoflSky; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +/** + * Comprehensive OneConfig configuration for CoflSky. + *

+ * This mirrors the settings structure maintained in the separate + * Coding/Windsurf/Cofl-Oneconfig project. Only the first half + * (General + Mod categories) is ported in this commit – remaining + * categories will be added subsequently to keep the diff readable. + */ +public class CoflConfig extends Config { + + /* ───────────────────────────── GENERAL ───────────────────────────── */ + + private transient static final String GENERAL = "General"; + private transient static final String MOD = "Mod"; + private transient static final String VISIBILITY = "Visibility"; + private transient static final String PRIVACY = "Privacy"; + + @Header(text = "Flipping Logic", size = OptionSize.DUAL) + public static boolean _hFlippingLogic = false; + + @Info(text = "General flipping-related parameters", size = OptionSize.DUAL, type = InfoType.INFO) + public static boolean _infoGen = false; + + // ─── Flipping logic ────────────────────────────────────────────────── + @Switch(name = "Based On Lowest BIN", description = "Calculate profit using current lowest BIN price", category = GENERAL) + public static boolean lbin = false; + + @Switch(name = "Only BIN Auctions", description = "Ignore auctions without BIN; snipe BINs only", category = GENERAL) + public static boolean onlyBin = true; + + @Switch(name = "Whitelist After Main Filters", description = "Apply whitelist filters after standard ones", category = GENERAL) + public static boolean whitelistAftermain = false; + + @Switch(name = "Disable Flips", description = "Completely disable flip suggestions", category = GENERAL) + public static boolean DisableFlips = false; + + @Switch(name = "Debug Mode", description = "Enable verbose logging for troubleshooting", category = GENERAL) + public static boolean DebugMode = false; + + @Switch(name = "Block High-Competition Flips", description = "Hide flips with too many competing listings", category = GENERAL) + public static boolean blockHighCompetition = false; + + @Slider(name = "Minimum Profit (Millions)", description = "Minimum raw coin profit required (in millions of coins)", min = 0f, max = 50f, step = 1, category = GENERAL) + public static int minProfit = 3; + + @Slider(name = "Minimum Profit %", description = "Minimum percentage profit required", min = 0f, max = 100f, step = 1, category = GENERAL) + public static int minProfitPercent = 10; + + @Slider(name = "Minimum Volume / 24h", description = "Minimum sales per day to qualify", min = 0f, max = 30f, step = 1, category = GENERAL) + public static int minVolume = 0; + + @Slider(name = "Maximum Cost (Millions)", description = "Upper price limit (in millions of coins)", min = 0f, max = 10_000f, step = 1, category = GENERAL) + public static int maxCost = 10_000; // 10,000 M = 10B + + /* Helper getters */ + public static long getMinProfitCoins() { + return minProfit * 1_000_000L; + } + + public static long getMaxCostCoins() { + return maxCost * 1_000_000L; + } + + // ─── Highlighting ──────────────────────────────────────────────────── + @Switch(name = "Highlight Filter Matches (AH/Trades)") + public static boolean loreHighlightFilterMatch = false; + + @Slider(name = "Min Profit for Highlight", min = 0f, max = 10_000_000f, step = 100_000) + public static float loreMinProfitForHighlight = 0f; + + @Switch(name = "Disable Highlighting") + public static boolean loreDisableHighlighting = false; + + // ─── Price Finder selection ───────────────────────────────────────── + @Header(text = "Allowed Price Finder Algorithms", size = OptionSize.DUAL) + public static boolean _hFinders = false; + + @Switch(name = "Flipper") + public static boolean finders = true; + + @Switch(name = "Sniper") + public static boolean finderSniper = true; + + @Switch(name = "Sniper (Median)") + public static boolean finderSniperMedian = true; + + @Switch(name = "User (Whitelist)") + public static boolean finderUserWhitelist = true; + + @Switch(name = "TFM") + public static boolean finderTFM = true; + + @Switch(name = "Stonks") + public static boolean finderStonks = true; + + @Switch(name = "Craftcost") + public static boolean finderCraftcost = true; + + + /* ───────────────────────────── MOD ───────────────────────────────── */ + + @Header(text = "", size = OptionSize.DUAL) + public static boolean _spModChatTop = false; + + @Header(text = "Chat & Sounds", size = OptionSize.DUAL) + public static boolean _hModChat = false; + + @Info(text = "Mod chat output & sound alerts", size = OptionSize.DUAL, type = InfoType.INFO) + public static boolean _infoModChat = false; + + // ─── Chat & Sound ─────────────────────────────────────────────────── + @Switch(name = "Enable Chat") + public static boolean modchat = false; + + @Switch(name = "Display Just Profit") + public static boolean modjustProfit = true; + + @Switch(name = "Play Sound On Flip") + public static boolean modsoundOnFlip = true; + + @Switch(name = "Short Numbers (M/k)") + public static boolean modshortNumbers = true; + + @Switch(name = "Short Item Names") + public static boolean modshortNames = false; + + @Switch(name = "Block Ten-Second Msg") + public static boolean modblockTenSecMsg = false; + + @Text(name = "Custom Format", placeholder = "%item% -> %profit% (e.g. Drill -> 3.2m)", secure = false) + public static String modformat = ""; + + @Text(name = "Custom Blocked Format", placeholder = "Blocked %item% (e.g. Blocked Drill)", secure = false) + public static String modblockedFormat = ""; + + /* Timer Widget */ + @Header(text = "Timer Widget", size = OptionSize.DUAL) + public static boolean _hModTimer = false; + + @Info(text = "HUD timer display options", size = OptionSize.DUAL, type = InfoType.INFO) + public static boolean _infoTimer = false; + + @Switch(name = "Display Timer") + public static boolean modcountdown = true; + + @Slider(name = "Timer X Position (%)", min = 0f, max = 100f, step = 1) + public static float modtimerX = 0f; + + @Slider(name = "Timer Y Position (%)", min = 0f, max = 100f, step = 1) + public static float modtimerY = 0f; + + @Slider(name = "Timer Show Seconds Before Update", min = 0f, max = 60f, step = 1) + public static float modtimerSeconds = 0f; + + @Slider(name = "Timer Scale", min = 0f, max = 4f, step = 1) + public static int modtimerScale = 1; + + @Slider(name = "Timer Precision (digits)", min = 0f, max = 5f, step = 1) + public static float modtimerPrecision = 0f; + + @Text(name = "Timer Prefix", placeholder = "Next flip in ", secure = false) + public static String modtimerPrefix = ""; + + /* Behaviour toggles */ + @Header(text = "Behaviour Toggles", size = OptionSize.DUAL) + public static boolean _hModBehaviour = false; + + @Info(text = "Advanced behaviour tweaks", size = OptionSize.DUAL, type = InfoType.INFO) + public static boolean _infoBeh = false; + + @Slider(name = "Minutes Between Blocked Msg", min = 0f, max = 127f, step = 1) + public static float modblockedMsg = 0f; + + @Slider(name = "Max % Of Purse Per Flip", min = 0f, max = 100f, step = 1) + public static float modmaxPercentOfPurse = 0f; + + @Slider(name = "AH List Time Target (h)", min = 0f, max = 24f, step = 1) + public static float modahListHours = 0f; + + @Slider(name = "Max Flip Items in Inventory", min = 0f, max = 54f, step = 1) + public static float modmaxItemsInInventory = 0f; + + @Switch(name = "No Bed Delay") + public static boolean modnoBedDelay = false; + + @Switch(name = "Streamer Mode") + public static boolean modstreamerMode = false; + + @Switch(name = "Auto-Start Flipper") + public static boolean modautoStartFlipper = false; + + @Switch(name = "Normal Sold Flips") + public static boolean modnormalSoldFlips = false; + + @Switch(name = "Temp Blacklist Spam") + public static boolean modtempBlacklistSpam = false; + + @Switch(name = "AH-Data-Only Mode") + public static boolean moddataOnlyMode = false; + + @Switch(name = "Quick Sell") + public static boolean modquickSell = false; + + @Switch(name = "Disable Spam Protection (⚠)") + public static boolean moddisableSpamProtection = false; + + /* ───────────────────────── VISIBILITY ───────────────────────────── */ + + @Header(text = "", size = OptionSize.DUAL) + public static boolean _spVisTop = false; + + @Header(text = "Visibility Options", size = OptionSize.DUAL) + public static boolean _hVisibility = false; + + @Info(text = "Item info display options", size = OptionSize.DUAL, type = InfoType.INFO) + public static boolean _infoVis = false; + + @Switch(name = "Show Cost") + public static boolean showcost = true; + + @Switch(name = "Show Estimated Profit") + public static boolean showestProfit = true; + + @Switch(name = "Show Lowest BIN") + public static boolean showlbin = false; + + @Switch(name = "Show 2nd Lowest BIN") + public static boolean showslbin = false; + + @Switch(name = "Show Median Price") + public static boolean showmedPrice = false; + + @Switch(name = "Show Seller Name") + public static boolean showseller = false; + + @Switch(name = "Show Volume") + public static boolean showvolume = true; + + @Switch(name = "Show Profit %") + public static boolean showprofitPercent = true; + + @Switch(name = "Show Profit (abs)") + public static boolean showprofit = false; + + @Switch(name = "Show Seller Open Button") + public static boolean showsellerOpenBtn = true; + + @Switch(name = "Show Item Lore") + public static boolean showlore = true; + + @Switch(name = "Hide Sold Auctions") + public static boolean showhideSold = false; + + @Switch(name = "Hide Manipulated Items") + public static boolean showhideManipulated = false; + + @Slider(name = "Extra Info Fields", min = 0f, max = 5f, step = 1) + public static float showextraFields = 0f; + + /* ────────────────────────── PRIVACY ─────────────────────────────── */ + + @Header(text = "", size = OptionSize.DUAL) + public static boolean _spPrivTop = false; + + @Header(text = "Privacy Settings", size = OptionSize.DUAL) + public static boolean _hPrivacy = false; + + @Info(text = "Telemetry & data collection", size = OptionSize.DUAL, type = InfoType.WARNING) + public static boolean _infoPriv = false; + + @Switch(name = "Collect Chat") + public static boolean privacyCollectChat = true; + + @Switch(name = "Collect Inventory") + public static boolean privacyCollectInventory = true; + + @Switch(name = "Disable Trade Storing") + public static boolean privacyDisableTradeStoring = false; + + @Switch(name = "Disable Kuudra Tracking") + public static boolean privacyDisableKuudraTracking = false; + + @Switch(name = "Collect Tab") + public static boolean privacyCollectTab = false; + + @Switch(name = "Collect Scoreboard") + public static boolean privacyCollectScoreboard = true; + + @Switch(name = "Collect Inv Click") + public static boolean privacyCollectInvClick = false; + + @Switch(name = "Collect Chat Clicks") + public static boolean privacyCollectChatClicks = true; + + @Switch(name = "Collect Lobby Changes") + public static boolean privacyCollectLobbyChanges = false; + + @Switch(name = "Collect Entities") + public static boolean privacyCollectEntities = false; + + @Switch(name = "Collect Location") + public static boolean privacyCollectLocation = false; + + @Switch(name = "Extend Descriptions") + public static boolean privacyExtendDescriptions = true; + + @Switch(name = "Auto Start") + public static boolean privacyAutoStart = true; + + /* ──────────────────────────── Cache Helpers ─────────────────────────── */ + + /** + * Retrieve a boolean config value by its server key, falling back to the static default + * value defined in this class. This avoids direct static-field references and ensures + * live values from {@link SettingsCache} are respected. + */ + public static boolean bool(String key, boolean fallback) { + return SettingsCache.getBool(key, fallback); + } + + public static int integer(String key, int fallback) { + return SettingsCache.getInt(key, fallback); + } + + public static long lng(String key, long fallback) { + return SettingsCache.getLong(key, fallback); + } + + public static double dbl(String key, double fallback) { + return SettingsCache.getDouble(key, fallback); + } + + public static String str(String key, String fallback) { + return SettingsCache.getString(key, fallback); + } + + /* ──────────────────────────── 1-1 Name Aliases ───────────────────────── */ + + // General + public static boolean DisableFlips() { return bool("DisableFlips", DisableFlips); } + public static boolean DebugMode() { return bool("DebugMode", DebugMode); } + public static boolean lbin() { return bool("lbin", lbin); } + public static boolean onlyBin() { return bool("onlyBin", onlyBin); } + public static boolean whitelistAftermain() { return bool("whitelistAftermain", whitelistAftermain); } + public static boolean blockHighCompetition() { return bool("BlockHighCompetitionFlips", blockHighCompetition); } + + // Visibility + public static boolean showcost() { return bool("showcost", showcost); } + public static boolean showprofitPercent() { return bool("showprofitPercent", showprofitPercent); } + + // Mod toggles (sample subset; add more as needed) + public static boolean modjustProfit() { return bool("modjustProfit", modjustProfit); } + public static boolean modchat() { return bool("modchat", modchat); } + public static boolean moddataOnlyMode() { return bool("moddataOnlyMode", moddataOnlyMode); } + public static boolean finders() { return bool("finders", finders); } + + // Helper to expose cache snapshot for debugging + public static java.util.Map dumpCache() { return SettingsCache.snapshot(); } + + /* ───────────────────────── Constructor ──────────────────────────── */ + /* Singleton instance to avoid duplicate OneConfig menu registrations */ + public static final CoflConfig INSTANCE = new CoflConfig(true); + + /** + * Opens the singleton GUI. + */ + public static void openGuiStatic() { + INSTANCE.openGui(); + } + + // private ctor flag to differentiate singleton creation + private CoflConfig(boolean dummy) { + super(new Mod(CoflSky.MODID, ModType.UTIL_QOL), CoflSky.MODID.toLowerCase() + "_config.json"); + initialize(); + } + + // legacy public constructor for code paths that still call new; delegates to singleton + public CoflConfig() { this(true); } + + /* ─────────── Integer alias sample ─────────── */ + public static int MinProfitPercent() { return integer("minProfitPercent", minProfitPercent); } + + @Override + public void save() { + super.save(); + pushUpdatesToServer(); + } + + /** + * Compare current static field values to {@link SettingsCache} and send any updates + * to the website via websocket using a Command of type "set". + */ + private void pushUpdatesToServer() { + try { + java.util.Map before = SettingsCache.snapshot(); + java.util.Map updateMap = new java.util.LinkedHashMap<>(); + for (java.lang.reflect.Field f : CoflConfig.class.getDeclaredFields()) { + if ((f.getModifiers() & java.lang.reflect.Modifier.STATIC) == 0) continue; + Class type = f.getType(); + if (!(type.isPrimitive() || type == String.class)) continue; + f.setAccessible(true); + Object value = f.get(null); + Object old = before.getOrDefault(f.getName(), null); + String valStr; + // convert millions to coins for maxCost + if ("maxCost".equals(f.getName()) && value instanceof Number) { + valStr = String.valueOf(((Number) value).longValue() * 1_000_000L); + } else { + valStr = String.valueOf(value); + } + String oldStr = old == null ? "" : String.valueOf(old); + if (!valStr.equals(oldStr)) { + SettingsCache.put(f.getName(), value); + updateMap.put(f.getName(), valStr); + } + } + if (!updateMap.isEmpty() && de.torui.coflsky.CoflSky.Wrapper != null) { + de.torui.coflsky.commands.Command> cmd = + new de.torui.coflsky.commands.Command<>(de.torui.coflsky.commands.CommandType.uploadSettings, updateMap); + de.torui.coflsky.CoflSky.Wrapper.SendMessage(cmd); + } + } catch (Exception ex) { + System.err.println("Failed to push settings to server: " + ex.getMessage()); + } + } +} diff --git a/src/main/java/de/torui/coflsky/config/SettingsCache.java b/src/main/java/de/torui/coflsky/config/SettingsCache.java new file mode 100644 index 0000000..4331aaf --- /dev/null +++ b/src/main/java/de/torui/coflsky/config/SettingsCache.java @@ -0,0 +1,58 @@ +package de.torui.coflsky.config; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Central store for server-provided option values. + * Key = option/field name as present in CoflConfig, value = parsed Object. + * Values are overwritten whenever the server sends a new settings blob. + */ +public final class SettingsCache { + private static final ConcurrentHashMap VALUES = new ConcurrentHashMap<>(); + + private SettingsCache() {} + + public static Map snapshot() { + return new ConcurrentHashMap<>(VALUES); + } + + public static void put(String key, Object value) { + if (key == null) return; + VALUES.put(key, value); + } + + public static Object get(String key) { + return VALUES.get(key); + } + + public static int getInt(String key, int def) { + Object v = VALUES.get(key); + return v instanceof Number ? ((Number) v).intValue() : def; + } + + public static boolean getBool(String key, boolean def) { + Object v = VALUES.get(key); + return v instanceof Boolean ? (Boolean) v : def; + } + + public static float getFloat(String key, float def) { + Object v = VALUES.get(key); + return v instanceof Number ? ((Number) v).floatValue() : def; + } + + public static double getDouble(String key, double def) { + Object v = VALUES.get(key); + return v instanceof Number ? ((Number) v).doubleValue() : def; + } + + public static long getLong(String key, long def) { + Object v = VALUES.get(key); + return v instanceof Number ? ((Number) v).longValue() : def; + } + + public static String getString(String key, String def) { + Object v = VALUES.get(key); + return v != null ? String.valueOf(v) : def; + } +} diff --git a/src/main/java/de/torui/coflsky/configuration/Configuration.java b/src/main/java/de/torui/coflsky/configuration/Configuration.java new file mode 100644 index 0000000..ed2de05 --- /dev/null +++ b/src/main/java/de/torui/coflsky/configuration/Configuration.java @@ -0,0 +1,46 @@ +package de.torui.coflsky.configuration; + +import de.torui.coflsky.CoflSky; +import de.torui.coflsky.handlers.EventRegistry; + +import java.util.regex.Pattern; + +public class Configuration { + public Configuration() { + + } + + private static Configuration instance; + + public static Configuration getInstance() { + if (instance == null) + instance = new Configuration(); + return instance; + } + + public static void setInstance(Configuration config) { + instance = config; + CoflSky.config.autoStart = instance.autoStart; + CoflSky.config.extendedtooltips = instance.extendDescriptions; + EventRegistry.chatpattern = Pattern.compile(instance.chatRegex, Pattern.CASE_INSENSITIVE);; + } + + public String chatRegex; + public boolean collectChat; + public boolean collectInventory; + public boolean collectTab; + public boolean collectScoreboard; + public boolean allowProxy; + public boolean collectInvClick; + public boolean collectChatClicks; + public boolean collectLobbyChanges; + public boolean collectEntities; + public boolean collectLocation; + public boolean autoStart; + @Description("Wherever or not to send item descriptions for extending to the server") + public boolean extendDescriptions; + + @Description("Chat input starting with one of these prefixes is sent to the server") + public String[] CommandPrefixes; + +} diff --git a/src/main/java/de/torui/coflsky/configuration/ConfigurationManager.java b/src/main/java/de/torui/coflsky/configuration/ConfigurationManager.java new file mode 100644 index 0000000..38dd525 --- /dev/null +++ b/src/main/java/de/torui/coflsky/configuration/ConfigurationManager.java @@ -0,0 +1,107 @@ +package de.torui.coflsky.configuration; + +import java.lang.reflect.Field; +import java.util.Arrays; + +import de.torui.coflsky.network.WSClient; +import net.minecraft.client.Minecraft; +import net.minecraft.event.HoverEvent; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.ChatStyle; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; + +public class ConfigurationManager { + + public Configuration Config; + + public ConfigurationManager() { + this.Config = Configuration.getInstance(); + } + + public void UpdateConfiguration(String data) { + + Configuration newConfig = WSClient.gson.fromJson(data, Configuration.class); + + if (newConfig == null) { + System.out.println("Could not deserialize configuration " + data); + } + + + try { + if (CompareProperties(Config, newConfig)) { + Configuration.setInstance(newConfig); + } + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public boolean CompareProperties(Configuration old, Configuration newConfiguration) + throws IllegalArgumentException, IllegalAccessException { + + int updatedProperties = 0; + for (Field f : Configuration.class.getFields()) { + + switch (f.getGenericType().getTypeName()) { + + case "int": + if (f.getInt(old) != f.getInt(newConfiguration)) { + UpdatedProperty(f,newConfiguration); + updatedProperties++; + } + break; + case "boolean": + if (f.getBoolean(old) != f.getBoolean(newConfiguration)) { + UpdatedProperty(f,newConfiguration); + updatedProperties++; + } + break; + case "java.lang.String": + + if (f.get(old) != null && !f.get(old).equals(f.get(newConfiguration))) { + UpdatedProperty(f,newConfiguration); + updatedProperties++; + } + break; + case "java.lang.String[]": + if (!Arrays.deepEquals((String[]) f.get(old), (String[]) f.get(newConfiguration))) { + UpdatedProperty(f,newConfiguration); + updatedProperties++; + } + break; + + default: + throw new RuntimeException("Invalid Configuration Type " + f.getGenericType().getTypeName()); + } + + } + + return updatedProperties > 0; + } + + private IChatComponent GetNameFormatted(Field propertyName) { + Description description = propertyName.getAnnotation(Description.class); + ChatComponentText toReturn = new ChatComponentText(propertyName.getName()); + + ChatStyle style = new ChatStyle(); + style.setColor(EnumChatFormatting.WHITE); + + if (description != null) { + style.setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ChatComponentText(description.value()))); + } + + return toReturn.setChatStyle(style); + + } + + public void UpdatedProperty(Field propertyName,Configuration confignew) throws IllegalAccessException { + System.out.println("The Configuration Setting " + propertyName.getName() + " has been updated to " + propertyName.get(confignew)); + } + +} diff --git a/src/main/java/de/torui/coflsky/configuration/Description.java b/src/main/java/de/torui/coflsky/configuration/Description.java new file mode 100644 index 0000000..4a54278 --- /dev/null +++ b/src/main/java/de/torui/coflsky/configuration/Description.java @@ -0,0 +1,20 @@ +/** + * + */ +package de.torui.coflsky.configuration; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(FIELD) +/** + * @author Florian Rinke + * + */ +public @interface Description { + public String value(); +} diff --git a/src/main/java/de/torui/coflsky/configuration/LocalConfig.java b/src/main/java/de/torui/coflsky/configuration/LocalConfig.java index bbcd8af..7e3c5f9 100644 --- a/src/main/java/de/torui/coflsky/configuration/LocalConfig.java +++ b/src/main/java/de/torui/coflsky/configuration/LocalConfig.java @@ -1,7 +1,7 @@ -package de.torui.CoflCore.CoflCore.configuration; +package de.torui.coflsky.configuration; import com.google.gson.Gson; -import CoflCore.configuration.GUIType; +import de.torui.coflsky.gui.GUIType; import java.io.File; import java.io.IOException; diff --git a/src/main/java/de/torui/coflsky/dynamic/ConfigClassGenerator.java b/src/main/java/de/torui/coflsky/dynamic/ConfigClassGenerator.java new file mode 100644 index 0000000..c265e8a --- /dev/null +++ b/src/main/java/de/torui/coflsky/dynamic/ConfigClassGenerator.java @@ -0,0 +1,106 @@ +package de.torui.coflsky.dynamic; + +import de.torui.coflsky.config.SettingsCache; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.NamingStrategy; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.StubMethod; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Generates configuration classes at runtime using ByteBuddy. + *

+ * Each generated class is fully annotated and exposes getters for all fields. + * A simple caching layer avoids redundant class generation. + */ +public final class ConfigClassGenerator { + + private ConfigClassGenerator() {} + + private static final Map> CACHE = new ConcurrentHashMap<>(); + + /** + * Generate a configuration POJO with the given fields. + * The resulting class contains private fields, public getters, and the supplied annotations. + * + * @param classNamePrefix prefix for the generated class name (will be suffixed with a random ID) + * @param fieldDefs map of field-name -> FieldSpec (type + annotations) + * @return loaded class + */ + public static Class generate(String classNamePrefix, Map fieldDefs) { + Objects.requireNonNull(classNamePrefix, "classNamePrefix"); + Objects.requireNonNull(fieldDefs, "fieldDefs"); + if (fieldDefs.isEmpty()) throw new IllegalArgumentException("fieldDefs must not be empty"); + + String signature = classNamePrefix + fieldDefs.hashCode(); + return CACHE.computeIfAbsent(signature, k -> createClass(classNamePrefix, fieldDefs)); + } + + /** + * Instantiate generated config class and populate values from SettingsCache. + */ + public static Object newInstance(Class cfgClass) { + try { + Object obj = cfgClass.getDeclaredConstructor().newInstance(); + for (Field field : cfgClass.getDeclaredFields()) { + field.setAccessible(true); + String key = field.getName(); + Object value = SettingsCache.get(key); + if (value != null && field.getType().isInstance(value)) { + field.set(obj, value); + } + } + return obj; + } catch (Exception e) { + throw new RuntimeException("Failed to create dynamic config instance", e); + } + } + + /* internal */ + private static Class createClass(String classNamePrefix, Map fieldDefs) { + ByteBuddy bb = new ByteBuddy().with(new NamingStrategy.SuffixingRandom(classNamePrefix)); + DynamicType.Builder builder = bb.subclass(Object.class) + .defineConstructor(Visibility.PUBLIC) + .intercept(StubMethod.INSTANCE); + + for (Map.Entry entry : fieldDefs.entrySet()) { + String name = entry.getKey(); + FieldSpec spec = entry.getValue(); + builder = builder.defineField(name, spec.getType(), Visibility.PRIVATE) + .annotateField(spec.getAnnotations()) + .defineMethod(getter(name), spec.getType(), Visibility.PUBLIC) + .intercept(FieldAccessor.ofField(name)) + .annotateMethod(spec.getAnnotations()); + } + + return builder.make() + .load(ConfigClassGenerator.class.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST) + .getLoaded(); + } + + private static String getter(String field) { + return "get" + Character.toUpperCase(field.charAt(0)) + field.substring(1); + } + + /** Lightweight description of a field specification */ + public static class FieldSpec { + private final Class type; + private final Annotation[] annotations; + + public FieldSpec(Class type, Annotation... annotations) { + this.type = type; + this.annotations = annotations != null ? annotations : new Annotation[0]; + } + + public Class getType() { return type; } + public Annotation[] getAnnotations() { return annotations; } + } +} diff --git a/src/main/java/de/torui/coflsky/gui/GUIType.java b/src/main/java/de/torui/coflsky/gui/GUIType.java new file mode 100644 index 0000000..586ac72 --- /dev/null +++ b/src/main/java/de/torui/coflsky/gui/GUIType.java @@ -0,0 +1,6 @@ +package de.torui.coflsky.gui; + +public enum GUIType { + TFM, + COFL +} diff --git a/src/main/java/de/torui/coflsky/gui/bingui/BinGuiCurrent.java b/src/main/java/de/torui/coflsky/gui/bingui/BinGuiCurrent.java index 1c44924..5308a9a 100644 --- a/src/main/java/de/torui/coflsky/gui/bingui/BinGuiCurrent.java +++ b/src/main/java/de/torui/coflsky/gui/bingui/BinGuiCurrent.java @@ -2,7 +2,7 @@ import de.torui.coflsky.CoflSky; -import CoflCore.configuration.GUIType; +import de.torui.coflsky.gui.GUIType; import de.torui.coflsky.gui.bingui.helper.ColorPallet; import de.torui.coflsky.gui.bingui.helper.RenderUtils; import de.torui.coflsky.handlers.EventHandler; @@ -73,7 +73,7 @@ public BinGuiCurrent(IInventory playerInventory, IInventory chestInventory, Stri } private boolean shouldSkip(GuiScreen screen) { - return !(screen instanceof GuiChest) || CoflCore.CoflCore.config.purchaseOverlay != GUIType.COFL || !EventHandler.isInSkyblock; + return !(screen instanceof GuiChest) || CoflSky.config.purchaseOverlay != GUIType.COFL || !EventHandler.isInSkyblock; } @SubscribeEvent(priority = EventPriority.HIGHEST) diff --git a/src/main/java/de/torui/coflsky/gui/bingui/BinGuiManager.java b/src/main/java/de/torui/coflsky/gui/bingui/BinGuiManager.java index 1dab38d..fe70177 100644 --- a/src/main/java/de/torui/coflsky/gui/bingui/BinGuiManager.java +++ b/src/main/java/de/torui/coflsky/gui/bingui/BinGuiManager.java @@ -1,7 +1,7 @@ package de.torui.coflsky.gui.bingui; import de.torui.coflsky.CoflSky; -import CoflCore.configuration.GUIType; +import de.torui.coflsky.gui.GUIType; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.inventory.GuiChest; import net.minecraft.inventory.InventoryBasic; @@ -10,12 +10,12 @@ public class BinGuiManager { public static void openNewFlipGui(String message, String extraData) { - if( CoflCore.CoflCore.config.purchaseOverlay != GUIType.COFL){ + if( CoflSky.config.purchaseOverlay != GUIType.COFL){ return; } GuiChest currentGui = new BinGuiCurrent(Minecraft.getMinecraft().thePlayer.inventory, new InventoryBasic("", false, 27), message, extraData); Mouse.setGrabbed(false); - Minecraft.getMinecraft().addScheduledTask(() -> Minecraft.getMinecraft().displayGuiScreen(currentGui)); + Minecraft.getMinecraft().displayGuiScreen(currentGui); } } \ No newline at end of file diff --git a/src/main/java/de/torui/coflsky/gui/bingui/helper/RenderUtils.java b/src/main/java/de/torui/coflsky/gui/bingui/helper/RenderUtils.java index 6c81fbf..64b2594 100644 --- a/src/main/java/de/torui/coflsky/gui/bingui/helper/RenderUtils.java +++ b/src/main/java/de/torui/coflsky/gui/bingui/helper/RenderUtils.java @@ -364,73 +364,4 @@ public static void setColor(Color color) { public static void rotate(float angle) { GL11.glRotatef(angle, 0.0F, 0.0F, 1.0F); } - - /** - * Renders green highlight boxes around blocks at specified coordinates for waypoints. - * @param blockCoords Array of block coordinates (each as int[3] {x, y, z}) - */ - public static void renderWaypointHighlightBoxes(int[][] blockCoords) { - GL11.glPushMatrix(); - // Disable depth test so highlight boxes render above everything - GL11.glDisable(GL11.GL_DEPTH_TEST); - - GL11.glEnable(GL11.GL_BLEND); - GL11.glDisable(GL11.GL_TEXTURE_2D); - GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - setColor(new Color(0, 255, 0, 128)); // semi-transparent green - - for (int[] coord : blockCoords) { - double x = coord[0] - mc.getRenderManager().viewerPosX; - double y = coord[1] - mc.getRenderManager().viewerPosY; - double z = coord[2] - mc.getRenderManager().viewerPosZ; - - GL11.glPushMatrix(); - GL11.glTranslated(x, y, z); - GL11.glLineWidth(2.0F); - - // Draw cube faces - GL11.glBegin(GL11.GL_QUADS); - // Bottom face - GL11.glVertex3d(0, 0, 0); - GL11.glVertex3d(1, 0, 0); - GL11.glVertex3d(1, 0, 1); - GL11.glVertex3d(0, 0, 1); - // Top face - GL11.glVertex3d(0, 1, 0); - GL11.glVertex3d(1, 1, 0); - GL11.glVertex3d(1, 1, 1); - GL11.glVertex3d(0, 1, 1); - // Front face - GL11.glVertex3d(0, 0, 1); - GL11.glVertex3d(1, 0, 1); - GL11.glVertex3d(1, 1, 1); - GL11.glVertex3d(0, 1, 1); - // Back face - GL11.glVertex3d(0, 0, 0); - GL11.glVertex3d(1, 0, 0); - GL11.glVertex3d(1, 1, 0); - GL11.glVertex3d(0, 1, 0); - // Left face - GL11.glVertex3d(0, 0, 0); - GL11.glVertex3d(0, 0, 1); - GL11.glVertex3d(0, 1, 1); - GL11.glVertex3d(0, 1, 0); - // Right face - GL11.glVertex3d(1, 0, 0); - GL11.glVertex3d(1, 0, 1); - GL11.glVertex3d(1, 1, 1); - GL11.glVertex3d(1, 1, 0); - GL11.glEnd(); - - GL11.glPopMatrix(); - } - - GL11.glEnable(GL11.GL_TEXTURE_2D); - GL11.glDisable(GL11.GL_BLEND); - - // Re-enable depth test after rendering - GL11.glEnable(GL11.GL_DEPTH_TEST); - - GL11.glPopMatrix(); - } } diff --git a/src/main/java/de/torui/coflsky/gui/tfm/ButtonRemapper.java b/src/main/java/de/torui/coflsky/gui/tfm/ButtonRemapper.java index ea8064a..c63f737 100644 --- a/src/main/java/de/torui/coflsky/gui/tfm/ButtonRemapper.java +++ b/src/main/java/de/torui/coflsky/gui/tfm/ButtonRemapper.java @@ -2,7 +2,7 @@ import de.torui.coflsky.CoflSky; import de.torui.coflsky.WSCommandHandler; -import CoflCore.configuration.GUIType; +import de.torui.coflsky.gui.GUIType; import de.torui.coflsky.handlers.EventHandler; import de.torui.coflsky.handlers.EventRegistry; import net.minecraft.client.Minecraft; @@ -165,7 +165,7 @@ private void drawCancelBox() { public void drawProfitInfo() { FontRenderer font = Minecraft.getMinecraft().fontRendererObj; - String text = CoflCore.CoflCore.flipHandler.lastClickedFlipMessage; + String text = WSCommandHandler.flipHandler.lastClickedFlipMessage; if (text == null) { return; @@ -243,7 +243,7 @@ public void drawConfirmButton(GuiChest currentScreen) { } private boolean shouldSkip(GuiScreen screen) { - return !(screen instanceof GuiChest) || CoflCore.CoflCore.config.purchaseOverlay != GUIType.TFM || !EventHandler.isInSkyblock; + return !(screen instanceof GuiChest) || CoflSky.config.purchaseOverlay != GUIType.TFM || !EventHandler.isInSkyblock; } @SubscribeEvent diff --git a/src/main/java/de/torui/coflsky/handlers/ForgeDescriptionHandler.java b/src/main/java/de/torui/coflsky/handlers/DescriptionHandler.java similarity index 76% rename from src/main/java/de/torui/coflsky/handlers/ForgeDescriptionHandler.java rename to src/main/java/de/torui/coflsky/handlers/DescriptionHandler.java index 4ecfe3a..0471893 100644 --- a/src/main/java/de/torui/coflsky/handlers/ForgeDescriptionHandler.java +++ b/src/main/java/de/torui/coflsky/handlers/DescriptionHandler.java @@ -1,8 +1,8 @@ package de.torui.coflsky.handlers; - -import CoflCore.classes.Position; -import de.torui.coflsky.minecraft_integration.PlayerDataProvider; +import de.torui.coflsky.Config; +import de.torui.coflsky.network.QueryServerCommands; +import de.torui.coflsky.network.WSClient; import de.torui.coflsky.utils.ReflectionUtil; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.inventory.GuiChest; @@ -25,17 +25,27 @@ import java.lang.invoke.MethodHandle; import java.util.ArrayList; import java.util.Base64; +import java.util.HashMap; import java.util.List; -public class ForgeDescriptionHandler { +public class DescriptionHandler { private static class InventoryWrapper { public String chestName; public String fullInventoryNbt; } + private static class DescModification { + public String type; + public String value; + public int line; + } + public static String allItemIds; + public static HashMap tooltipItemIdMap = new HashMap<>(); + + public static final DescModification[] EMPTY_ARRAY = new DescModification[0]; public static final NBTTagCompound EMPTY_COMPOUND = new NBTTagCompound(); private boolean IsOpen = true; @@ -74,9 +84,14 @@ public static String ExtractIdFromItemStack(ItemStack stack) { return ExtractStackableIdFromItemStack(stack); } - private CoflCore.handlers.DescriptionHandler.DescModification[] getTooltipData(ItemStack itemStack) { + private DescModification[] getTooltipData(ItemStack itemStack) { String id = ExtractIdFromItemStack(itemStack); - return CoflCore.handlers.DescriptionHandler.getTooltipData(id); + if (tooltipItemIdMap.containsKey(id)) { + return tooltipItemIdMap.getOrDefault(id, EMPTY_ARRAY); + } + shouldUpdate = true; + + return EMPTY_ARRAY; } /** @@ -99,9 +114,6 @@ public void loadDescriptionAndListenForChanges(GuiOpenEvent event) { } catch (InterruptedException e) { throw new RuntimeException(e); } - if(!IsOpen) { - break; - } if (shouldUpdate || hasAnyStackChanged(gc)) { shouldUpdate = loadDescriptionForInventory(event, gc, true); // reduce update time since its more likely that more changes occure after one @@ -128,43 +140,9 @@ private static String getCurrentInventoryIds(GuiContainer gc) { return builder.toString(); } - public static void uploadInventory() { - net.minecraft.client.Minecraft mc = net.minecraft.client.Minecraft.getMinecraft(); - if (mc.thePlayer == null || mc.thePlayer.inventory == null) { - return; - } - NBTTagCompound compound = new NBTTagCompound(); - NBTTagList tl = new NBTTagList(); - List items = new ArrayList<>(); - List itemIds = new ArrayList<>(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (int i = 0; i < mc.thePlayer.inventory.getSizeInventory(); i++) { - ItemStack stack = mc.thePlayer.inventory.getStackInSlot(i); - String id = ExtractIdFromItemStack(stack); - itemIds.add(id); - items.add(stack); - if (stack != null) { - tl.appendTag(stack.serializeNBT()); - } else { - tl.appendTag(EMPTY_COMPOUND); - } - } - try - { - compound.setTag("i", tl); - CompressedStreamTools.writeCompressed(compound, baos); - - CoflCore.handlers.DescriptionHandler.loadDescriptionForInventory(itemIds.toArray(new String[0]), "Crafting", Base64.getEncoder().encodeToString(baos.toByteArray()), PlayerDataProvider.getUsername()); - - } catch (IOException e) { - e.printStackTrace(); - } - } - private static boolean loadDescriptionForInventory(GuiOpenEvent event, GuiContainer gc, boolean skipLoadCheck) { InventoryWrapper wrapper = new InventoryWrapper(); - Position pos = null; - if (event.gui != null && event.gui instanceof GuiChest) { + if (event.gui instanceof GuiChest) { if (!skipLoadCheck) waitForChestContentLoad(event, gc); @@ -175,7 +153,7 @@ private static boolean loadDescriptionForInventory(GuiOpenEvent event, GuiContai wrapper.chestName = chestName; BlockPos chestPos = ChestUtils.getLookedAtChest(); if (chestPos != null && chestName.endsWith("hest")) { - pos = new Position(chestPos.getX(), chestPos.getY(), chestPos.getZ()); + wrapper.chestName += chestPos.toString(); } } } @@ -203,18 +181,14 @@ private static boolean loadDescriptionForInventory(GuiOpenEvent event, GuiContai wrapper.fullInventoryNbt = Base64.getEncoder().encodeToString(baos.toByteArray()); List stacks = new ArrayList<>(); - List itemIds = new ArrayList<>(); for (Slot obj : gc.inventorySlots.inventorySlots) { - ItemStack stack = obj.getStack(); - stacks.add(stack); - String id = ExtractIdFromItemStack(stack); - itemIds.add(id); + stacks.add(obj.getStack()); } - System.out.println("Loading description for inventory: " + wrapper.chestName + " with " + itemIds.size() + " items"); - CoflCore.handlers.DescriptionHandler.loadDescriptionForInventory(itemIds.toArray(new String[0]), wrapper.chestName, wrapper.fullInventoryNbt, PlayerDataProvider.getUsername(), pos); + String data = WSClient.gson.toJson(wrapper); + String info = QueryServerCommands.PostRequest(Config.BaseUrl + "/api/mod/description/modifications", data); - /* TODO: migrate this + DescModification[][] arr = WSClient.gson.fromJson(info, DescModification[][].class); for (int i = 0; i < stacks.size(); i++) { ItemStack stack = stacks.get(i); String id = ExtractIdFromItemStack(stack); @@ -230,7 +204,7 @@ private static boolean loadDescriptionForInventory(GuiOpenEvent event, GuiContai shouldGetRefreshed = true; } } - }*/ + } } catch (IOException e) { e.printStackTrace(); @@ -254,13 +228,12 @@ private static void waitForChestContentLoad(GuiOpenEvent event, GuiContainer gc) } public void setTooltips(ItemTooltipEvent event) { - CoflCore.handlers.DescriptionHandler.DescModification[] data = getTooltipData(event.itemStack); + DescModification[] data = getTooltipData(event.itemStack); - if (data == null || data.length == 0){ + if (data == null || data.length == 0) return; - } - for (CoflCore.handlers.DescriptionHandler.DescModification datum : data) { + for (DescModification datum : data) { if (event.toolTip.size() <= datum.line) { System.out.println( "Skipped line modification " + datum.line + " for " + event.itemStack.getDisplayName()); @@ -294,8 +267,8 @@ public void highlightSlots(GuiScreenEvent.BackgroundDrawnEvent event) { for (Slot inventorySlot : containerGui.inventorySlots.inventorySlots) { if (!inventorySlot.getHasStack()) continue; - CoflCore.handlers.DescriptionHandler.DescModification[] tooltipData = getTooltipData(inventorySlot.getStack()); - for (CoflCore.handlers.DescriptionHandler.DescModification modification : tooltipData) { + DescModification[] tooltipData = getTooltipData(inventorySlot.getStack()); + for (DescModification modification : tooltipData) { if ("HIGHLIGHT".equals(modification.type)) { int color = (int) (Long.parseLong(modification.value, 16) & 0xFFFFFFFFL); try { @@ -320,6 +293,6 @@ public void highlightSlots(GuiScreenEvent.BackgroundDrawnEvent event) { * Called when the inventory is closed */ public static void emptyTooltipData() { - //TODO: clear tooltip data + tooltipItemIdMap.clear(); } } diff --git a/src/main/java/de/torui/coflsky/handlers/EventHandler.java b/src/main/java/de/torui/coflsky/handlers/EventHandler.java index fdc9645..0df0a2e 100644 --- a/src/main/java/de/torui/coflsky/handlers/EventHandler.java +++ b/src/main/java/de/torui/coflsky/handlers/EventHandler.java @@ -1,10 +1,9 @@ package de.torui.coflsky.handlers; -import CoflCore.CoflCore; import de.torui.coflsky.CoflSky; -import CoflCore.commands.Command; -import CoflCore.commands.CommandType; -import CoflCore.configuration.Configuration; +import de.torui.coflsky.commands.Command; +import de.torui.coflsky.commands.CommandType; +import de.torui.coflsky.configuration.Configuration; import de.torui.coflsky.minecraft_integration.PlayerDataProvider; import de.torui.coflsky.minecraft_integration.PlayerDataProvider.PlayerPosition; import net.minecraft.client.Minecraft; @@ -19,7 +18,7 @@ import net.minecraft.util.EnumChatFormatting; import java.util.*; -import static CoflCore.CoflCore.config; +import static de.torui.coflsky.CoflSky.config; import static java.lang.Long.parseLong; public class EventHandler { @@ -32,7 +31,8 @@ public class EventHandler { private static String server = ""; public static void TabMenuData() { - if (isInSkyblock && CoflCore.Wrapper.isRunning && Configuration.getInstance().collectTab) { + boolean collectTab = de.torui.coflsky.config.SettingsCache.getBool("collectTab", true); + if (isInSkyblock && CoflSky.Wrapper.isRunning && collectTab) { List tabdata = getTabList(); int size = tabdata.size() - 1; for (int i = 0; i < tabdata.size(); i++) { @@ -43,17 +43,17 @@ public static void TabMenuData() { } public static void UploadTabData() { - if (!CoflCore.Wrapper.isRunning) + if (!CoflSky.Wrapper.isRunning) return; Command> data = new Command<>(CommandType.uploadTab, getTabList()); - CoflCore.Wrapper.SendMessage(data); + CoflSky.Wrapper.SendMessage(data); } public static void UploadScoreboardData() { - if (!CoflCore.Wrapper.isRunning) + if (!CoflSky.Wrapper.isRunning) return; Command> data = new Command<>(CommandType.uploadScoreboard, getScoreboard()); - CoflCore.Wrapper.SendMessage(data); + CoflSky.Wrapper.SendMessage(data); } public static void ScoreboardData() { @@ -66,7 +66,7 @@ public static void ScoreboardData() { return; } checkIfInSkyblock(s); - if (!isInSkyblock || !CoflCore.Wrapper.isRunning) + if (!isInSkyblock || !CoflSky.Wrapper.isRunning) return; List scoreBoardLines = getScoreboard(); @@ -74,13 +74,17 @@ public static void ScoreboardData() { boolean foundPurse = false; for (int i = 0; i < scoreBoardLines.size(); i++) { String line = EnumChatFormatting.getTextWithoutFormattingCodes(scoreBoardLines.get(size - i).toLowerCase()); - if (Configuration.getInstance().collectScoreboard) { + boolean collectScoreboard = de.torui.coflsky.config.SettingsCache.getBool("collectScoreboard", true); + if (collectScoreboard) { if(ProcessScoreboard(line)) foundPurse = true; } - if (line.contains("⏣") && !line.equals(location)) { + boolean collectLocation = de.torui.coflsky.config.SettingsCache.getBool("collectLocation", false); + if (line.contains("⏣") && collectLocation && !line.equals(location)) { location = line; try { + Thread.sleep(20); + UploadLocation(); Thread.sleep(20); UploadScoreboardData(); } catch (InterruptedException e) { @@ -92,18 +96,19 @@ public static void ScoreboardData() { { purse = -1; // no purse found, sync that to server Command data = new Command<>(CommandType.updatePurse, purse); - CoflCore.Wrapper.SendMessage(data); + CoflSky.Wrapper.SendMessage(data); UploadScoreboardData(); UploadTabData(); } } private static void UploadLocation() { - if (!Configuration.getInstance().collectLocation) + boolean collectLocation = de.torui.coflsky.config.SettingsCache.getBool("collectLocation", false); + if (!collectLocation) return; Command data = new Command<>(CommandType.updateLocation, PlayerDataProvider.getPlayerPosition()); - CoflCore.Wrapper.SendMessage(data); + CoflSky.Wrapper.SendMessage(data); } private static List getScoreboard() { @@ -165,13 +170,13 @@ private static void ProcessTabMenu(String line) { private static void checkIfInSkyblock(String s) { if ((s.contains("SKYBLOCK") || s.contains("E")) && !isInSkyblock) { if (config.autoStart) { - CoflCore.Wrapper.stop(); - CoflCore.Wrapper.startConnection(PlayerDataProvider.getUsername()); + CoflSky.Wrapper.stop(); + CoflSky.Wrapper.startConnection(); } isInSkyblock = true; } else if (!s.contains("SKYBLOCK") && !s.contains("E") && isInSkyblock) { if (config.autoStart) { - CoflCore.Wrapper.stop(); + CoflSky.Wrapper.stop(); Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("connection to ") .appendSibling(new ChatComponentText("C").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.DARK_BLUE))) .appendSibling(new ChatComponentText("oflnet").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD))) @@ -199,7 +204,7 @@ private static boolean ProcessScoreboard(String line) { if (purse != purse_) { purse = purse_; Command data = new Command<>(CommandType.updatePurse, purse); - CoflCore.Wrapper.SendMessage(data); + CoflSky.Wrapper.SendMessage(data); UploadLocation(); } return true; @@ -213,7 +218,7 @@ private static boolean ProcessScoreboard(String line) { if (bits != bits_) { bits = bits_; Command data = new Command<>(CommandType.updateBits, bits); - CoflCore.Wrapper.SendMessage(data); + CoflSky.Wrapper.SendMessage(data); } return true; } diff --git a/src/main/java/de/torui/coflsky/handlers/EventRegistry.java b/src/main/java/de/torui/coflsky/handlers/EventRegistry.java index 49cad00..f4f67ae 100644 --- a/src/main/java/de/torui/coflsky/handlers/EventRegistry.java +++ b/src/main/java/de/torui/coflsky/handlers/EventRegistry.java @@ -2,48 +2,44 @@ import java.io.ByteArrayOutputStream; import java.time.LocalDateTime; -import java.util.*; +import java.util.Base64; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.HashMap; +import java.util.Map; -// Removed swing KeyBinding import +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; -import CoflCore.handlers.DescriptionHandler; import com.mojang.realmsclient.util.Pair; import de.torui.coflsky.CoflSky; import de.torui.coflsky.WSCommandHandler; -import CoflCore.commands.Command; -import CoflCore.commands.CommandType; -import CoflCore.commands.JsonStringCommand; -import CoflCore.commands.models.AuctionData; -import CoflCore.commands.models.FlipData; -import CoflCore.commands.models.HotkeyRegister; -import CoflCore.network.WSClient; -import de.torui.coflsky.gui.bingui.helper.RenderUtils; -import de.torui.coflsky.mixins.AccessorGuiEditSign; +import de.torui.coflsky.commands.Command; +import de.torui.coflsky.commands.CommandType; +import de.torui.coflsky.commands.JsonStringCommand; +import de.torui.coflsky.commands.models.AuctionData; +import de.torui.coflsky.commands.models.FlipData; +import de.torui.coflsky.commands.models.HotkeyRegister; +import de.torui.coflsky.configuration.Configuration; +import de.torui.coflsky.network.WSClient; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.inventory.GuiEditSign; -import net.minecraft.client.gui.inventory.GuiInventory; import net.minecraft.client.settings.KeyBinding; import net.minecraft.client.gui.inventory.GuiChest; import net.minecraft.client.gui.inventory.GuiContainer; -import net.minecraft.event.HoverEvent; import net.minecraft.inventory.ContainerChest; import net.minecraft.inventory.IInventory; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.tileentity.TileEntitySign; -import net.minecraft.util.ChatComponentText; -import net.minecraft.util.ChatStyle; -import net.minecraft.util.IChatComponent; import net.minecraft.util.ResourceLocation; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; -import net.minecraftforge.client.event.*; +import net.minecraftforge.client.event.ClientChatReceivedEvent; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType; import net.minecraftforge.event.entity.player.ItemTooltipEvent; import net.minecraftforge.fml.client.registry.ClientRegistry; @@ -52,15 +48,13 @@ import net.minecraftforge.fml.common.gameevent.InputEvent; import net.minecraftforge.fml.common.gameevent.InputEvent.KeyInputEvent; import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.fml.common.gameevent.TickEvent; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; -import org.lwjgl.opengl.GL11; -import static CoflCore.CoflCore.config; -import static CoflCore.handlers.DescriptionHandler.*; +import static de.torui.coflsky.CoflSky.config; +import static de.torui.coflsky.CoflSky.keyBindings; +import static de.torui.coflsky.handlers.DescriptionHandler.*; import static de.torui.coflsky.handlers.EventHandler.*; public class EventRegistry { @@ -70,9 +64,9 @@ public class EventRegistry { @SubscribeEvent public void onDisconnectedFromServerEvent(ClientDisconnectionFromServerEvent event) { - if (CoflCore.CoflCore.Wrapper.isRunning) { + if (CoflSky.Wrapper.isRunning) { System.out.println("Disconnected from server"); - CoflCore.CoflCore.Wrapper.stop(); + CoflSky.Wrapper.stop(); EventHandler.isInSkyblock = false; System.out.println("CoflSky stopped"); } @@ -81,7 +75,7 @@ public void onDisconnectedFromServerEvent(ClientDisconnectionFromServerEvent eve public static long LastClick = System.currentTimeMillis(); public static Boolean LastHotkeyState; public static Boolean LastEventButtonState; - private ForgeDescriptionHandler forgeDescriptionHandler; + private DescriptionHandler descriptionHandler; @SideOnly(Side.CLIENT) @SubscribeEvent(priority = EventPriority.NORMAL, receiveCanceled = true) @@ -106,7 +100,7 @@ public void onKeyEvent(KeyInputEvent event) { public static void onAfterKeyPressed() { if (CoflSky.keyBindings[0].isPressed()) { if (WSCommandHandler.lastOnClickEvent != null) { - FlipData f = CoflCore.CoflCore.flipHandler.fds.GetLastFlip(); + FlipData f = WSCommandHandler.flipHandler.fds.GetLastFlip(); if (f != null) { WSCommandHandler.Execute("/cofl openauctiongui " + f.Id + " false", Minecraft.getMinecraft().thePlayer); @@ -116,7 +110,7 @@ public static void onAfterKeyPressed() { if (CoflSky.keyBindings[1].isKeyDown()) { if ((System.currentTimeMillis() - LastClick) >= 300) { - FlipData f = CoflCore.CoflCore.flipHandler.fds.GetHighestFlip(); + FlipData f = WSCommandHandler.flipHandler.fds.GetHighestFlip(); if (f != null) { WSCommandHandler.Execute("/cofl openauctiongui " + f.Id + " true", @@ -126,7 +120,7 @@ public static void onAfterKeyPressed() { LastClick = System.currentTimeMillis(); String command = WSClient.gson.toJson("/viewauction " + f.Id); - CoflCore.CoflCore.Wrapper.SendMessage(new JsonStringCommand(CommandType.Clicked, command)); + CoflSky.Wrapper.SendMessage(new JsonStringCommand(CommandType.Clicked, command)); WSCommandHandler.Execute("/cofl track besthotkey " + f.Id, Minecraft.getMinecraft().thePlayer); } else { // only display message once (if this is the key down event) @@ -181,12 +175,6 @@ private static String getContextToAppend() { return toAppend; } - @SubscribeEvent - public void onDimensionLoad(net.minecraftforge.event.world.WorldEvent.Load event) { - // Callback for when a dimension (world) is loaded, in skyblock used when switching islands - WSCommandHandler.highlightCoordinates = new int[0][]; - } - @SideOnly(Side.CLIENT) // @SubscribeEvent public void DrawOntoGUI(RenderGameOverlayEvent rgoe) { @@ -194,7 +182,7 @@ public void DrawOntoGUI(RenderGameOverlayEvent rgoe) { if (rgoe.type == ElementType.CROSSHAIRS) { Minecraft mc = Minecraft.getMinecraft(); mc.ingameGUI.drawString(Minecraft.getMinecraft().fontRendererObj, - "Flips in Pipeline:" + CoflCore.CoflCore.flipHandler.fds.CurrentFlips(), 0, 0, Integer.MAX_VALUE); + "Flips in Pipeline:" + WSCommandHandler.flipHandler.fds.CurrentFlips(), 0, 0, Integer.MAX_VALUE); } } @@ -228,6 +216,7 @@ public static String ExtractUuidFromInventory(IInventory inventory) { if (uuid.length() == 0) { throw new Exception(); } + System.out.println("Item has the UUID: " + uuid); return uuid; } catch (Exception e) { System.out.println( @@ -242,21 +231,47 @@ public static String ExtractUuidFromInventory(IInventory inventory) { public static final Pair> EMPTY = Pair.of(null, Pair.of("", LocalDateTime.MIN)); public static Pair> last = EMPTY; + private LocalDateTime lastBatchStart = LocalDateTime.now(); + private LinkedBlockingQueue chatBatch = new LinkedBlockingQueue(); @SubscribeEvent public void HandleChatEvent(ClientChatReceivedEvent sce) { - CoflCore.handlers.EventRegistry.onChatMessage(sce.message.getUnformattedText()); - String previousHover = null; - for (IChatComponent component : sce.message.getSiblings()) { - if(component.getChatStyle().getChatHoverEvent() != null - && component.getChatStyle().getChatHoverEvent().getAction() == HoverEvent.Action.SHOW_TEXT) { - String text = component.getChatStyle().getChatHoverEvent().getValue().getUnformattedText(); - if (text.equals(previousHover)) - continue; // skip if the text is the same as the previous one, different colored text often has the same hover text - previousHover = text; - CoflCore.handlers.EventRegistry.onChatMessage(text); + // use SettingsCache instead of legacy Configuration + boolean collectChat = de.torui.coflsky.config.SettingsCache.getBool("collectChat", true); + if (!CoflSky.Wrapper.isRunning || !collectChat) + return; + chatThreadPool.submit(() -> { + try { + + String msg = sce.message.getUnformattedText(); + Matcher matcher = chatpattern.matcher(msg); + boolean matchFound = matcher.find(); + if (!matchFound) + return; + + chatBatch.add(msg); + // add 500ms to the last batch start time + long nanoSeconds = 500_000_000; + if (!lastBatchStart.plusNanos(nanoSeconds).isBefore(LocalDateTime.now())) { + System.out.println(msg + " was not sent because it was too soon"); + return; + } + lastBatchStart = LocalDateTime.now(); + + new java.util.Timer().schedule(new java.util.TimerTask() { + @Override + public void run() { + System.out.println("Sending batch of " + chatBatch.size() + " messages"); + Command data = new Command<>(CommandType.chatBatch, chatBatch.toArray(new String[0])); + chatBatch.clear(); + CoflSky.Wrapper.SendMessage(data); + } + }, 500); + + } catch (Exception e) { + e.printStackTrace(); } - } + }); } public static long lastStartTime = Long.MIN_VALUE; @@ -267,7 +282,7 @@ public void HandleChatEvent(ClientChatReceivedEvent sce) { @SideOnly(Side.CLIENT) @SubscribeEvent public void OnGuiClick(GuiScreenEvent.MouseInputEvent mie) { - if (!CoflCore.CoflCore.Wrapper.isRunning) + if (!CoflSky.Wrapper.isRunning) return; if (!(mie.gui instanceof GuiChest)) return; // verify that it's really a chest @@ -299,7 +314,7 @@ public void OnGuiClick(GuiScreenEvent.MouseInputEvent mie) { } Command data = new Command<>(CommandType.PurchaseStart, ad); - CoflCore.CoflCore.Wrapper.SendMessage(data); + CoflSky.Wrapper.SendMessage(data); System.out.println("PurchaseStart"); last = Pair.of("You claimed ", Pair.of(itemUUID, LocalDateTime.now())); lastStartTime = System.currentTimeMillis() + 200 /* ensure a small debounce */; @@ -315,69 +330,6 @@ public void OnRenderTick(TickEvent.RenderTickEvent event) { de.torui.coflsky.minecraft_integration.CountdownTimer.onRenderTick(event); } - @SubscribeEvent - public void highlightChests(DrawBlockHighlightEvent e){ - if (WSCommandHandler.highlightCoordinates.length > 0) { - RenderUtils.renderWaypointHighlightBoxes(WSCommandHandler.highlightCoordinates); - } - } - - @SubscribeEvent - public void onGuiDraw(GuiScreenEvent.DrawScreenEvent.Post event) { - Minecraft mc = Minecraft.getMinecraft(); - - // Check if the current GUI screen is the player's inventory or a chest GUI - if (event.gui instanceof GuiContainer) { - DescriptionHandler.DescModification[] toDisplay = DescriptionHandler.getInfoDisplay(); - if(toDisplay.length == 0) - return; // No info to display, exit early - GuiContainer inventoryGui = (GuiContainer) event.gui; - FontRenderer fontRenderer = mc.fontRendererObj; - - // --- Get the actual rendered top-left position of the inventory GUI --- - // 'guiLeft' and 'guiTop' are protected but directly accessible from the instance - // (or via a getter if one existed, but direct access is common for protected fields in mods). - // This position accounts for potion effect shifts. - int inventoryGuiLeft = inventoryGui.guiLeft; - int inventoryGuiTop = inventoryGui.guiTop; - - // --- Define your info text lines --- - ArrayList lines = new ArrayList<>(); - int maxWidth = 0; - for(DescriptionHandler.DescModification mod : toDisplay) { - if (mod != null && mod.value != null) { - if(mod.type.equals("APPEND")) { - lines.add(mod.value); - int width = fontRenderer.getStringWidth(mod.value); - if (width > maxWidth) { - maxWidth = width; - } - } - else if(mod.type.equals("SUGGEST")) { - lines.add("§7Will suggest: §r" + mod.value.split(": ")[1]); - } - } - } - - // Start position for the text on the left. - // (inventoryGuiLeft - padding - maxTextWidth) will place the right edge of the text - // 'padding' pixels to the left of the inventory's left edge. - int textX = inventoryGuiLeft - 5 - maxWidth; - int textY = inventoryGuiTop + 5; - - net.minecraft.client.renderer.GlStateManager.pushMatrix(); - net.minecraft.client.renderer.GlStateManager.enableBlend(); // Enable blending for transparency - net.minecraft.client.renderer.GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // Standard alpha blend function - for(int i = 0; i < lines.size(); i++) { - String line = lines.get(i); - // Draw each line with a vertical offset - fontRenderer.drawString(line, textX, textY + (fontRenderer.FONT_HEIGHT + 2) * i, 0xFFFFFFFF, true); // White - } - net.minecraft.client.renderer.GlStateManager.disableBlend(); // Disable blending - net.minecraft.client.renderer.GlStateManager.popMatrix(); - } - } - long UpdateThisTick = 0; @SubscribeEvent(priority = EventPriority.LOW) @@ -402,35 +354,13 @@ public void onGuiOpen(GuiOpenEvent event) { // therefore clear the lastClickFlipMessage, so it doesn't show on other // auctions if (event.gui == null) { - CoflCore.CoflCore.flipHandler.lastClickedFlipMessage = ""; - } - - if (event.gui instanceof GuiEditSign) { - GuiEditSign signGui = (GuiEditSign) event.gui; - DescriptionHandler.DescModification[] toDisplay = DescriptionHandler.getInfoDisplay(); - for (DescriptionHandler.DescModification mod : toDisplay) { - if (mod == null || !mod.type.equals("SUGGEST")) { - continue; - } - String[] parts = mod.value.split(": "); - if (parts.length < 2) - return; - try { - TileEntitySign tileEntitySign = ((AccessorGuiEditSign) signGui).getTileSign(); - if(tileEntitySign.signText[3].getUnformattedText().contains(parts[0])) - tileEntitySign.signText[0] = new ChatComponentText(parts[1]); - } catch (RuntimeException e) { - System.err.println("Failed to access tileSign field in GuiEditSign: " + e.getMessage()); - e.printStackTrace(); - } - } - return; + WSCommandHandler.flipHandler.lastClickedFlipMessage = ""; } if (!config.extendedtooltips) return; - if (forgeDescriptionHandler != null) - forgeDescriptionHandler.Close(); + if (descriptionHandler != null) + descriptionHandler.Close(); if (event.gui == null) emptyTooltipData(); @@ -438,8 +368,8 @@ public void onGuiOpen(GuiOpenEvent event) { return; new Thread(() -> { try { - forgeDescriptionHandler = new ForgeDescriptionHandler(); - forgeDescriptionHandler.loadDescriptionAndListenForChanges(event); + descriptionHandler = new DescriptionHandler(); + descriptionHandler.loadDescriptionAndListenForChanges(event); } catch (Exception e) { System.out.println("failed to update description " + e); } @@ -448,16 +378,16 @@ public void onGuiOpen(GuiOpenEvent event) { @SubscribeEvent public void onBackgroundRenderDone(GuiScreenEvent.BackgroundDrawnEvent event) { - if (forgeDescriptionHandler != null) - forgeDescriptionHandler.highlightSlots(event); + if (descriptionHandler != null) + descriptionHandler.highlightSlots(event); } @SubscribeEvent(priority = EventPriority.HIGHEST) public void onItemTooltipEvent(ItemTooltipEvent event) { if (!config.extendedtooltips) return; - if (forgeDescriptionHandler == null) + if (descriptionHandler == null) return; - forgeDescriptionHandler.setTooltips(event); + descriptionHandler.setTooltips(event); } } diff --git a/src/main/java/de/torui/coflsky/init/AutoDiscoveryMixinPlugin.java b/src/main/java/de/torui/coflsky/init/AutoDiscoveryMixinPlugin.java deleted file mode 100644 index 51589ab..0000000 --- a/src/main/java/de/torui/coflsky/init/AutoDiscoveryMixinPlugin.java +++ /dev/null @@ -1,188 +0,0 @@ -package de.torui.coflsky.init; - -import org.spongepowered.asm.lib.tree.ClassNode; -import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; -import org.spongepowered.asm.mixin.extensibility.IMixinInfo; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.stream.Stream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -/** - * A mixin plugin to automatically discover all mixins in the current JAR. - *

- * This mixin plugin automatically scans your entire JAR (or class directory, in case of an in-IDE launch) for classes inside of your - * mixin package and registers those. It does this recursively for sub packages of the mixin package as well. This means you will need - * to only have mixin classes inside of your mixin package, which is good style anyway. - * - * @author Linnea Gräf - */ -public class AutoDiscoveryMixinPlugin implements IMixinConfigPlugin { - private static final List mixinPlugins = new ArrayList<>(); - - public static List getMixinPlugins() { - return mixinPlugins; - } - - private String mixinPackage; - - @Override - public void onLoad(String mixinPackage) { - this.mixinPackage = mixinPackage; - mixinPlugins.add(this); - } - - /** - * Resolves the base class root for a given class URL. This resolves either the JAR root, or the class file root. - * In either case the return value of this + the class name will resolve back to the original class url, or to other - * class urls for other classes. - */ - public URL getBaseUrlForClassUrl(URL classUrl) { - String string = classUrl.toString(); - if (classUrl.getProtocol().equals("jar")) { - try { - return new URL(string.substring(4).split("!")[0]); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - if (string.endsWith(".class")) { - try { - return new URL(string.replace("\\", "/") - .replace(getClass().getCanonicalName() - .replace(".", "/") + ".class", "")); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - return classUrl; - } - - /** - * Get the package that contains all the mixins. This value is set by mixin itself using {@link #onLoad}. - */ - public String getMixinPackage() { - return mixinPackage; - } - - /** - * Get the path inside the class root to the mixin package - */ - public String getMixinBaseDir() { - return mixinPackage.replace(".", "/"); - } - - /** - * A list of all discovered mixins. - */ - private List mixins = null; - - /** - * Try to add mixin class ot the mixins based on the filepath inside of the class root. - * Removes the {@code .class} file suffix, as well as the base mixin package. - *

This method cannot be called after mixin initialization.

- * - * @param className the name or path of a class to be registered as a mixin. - */ - public void tryAddMixinClass(String className) { - String norm = (className.endsWith(".class") ? className.substring(0, className.length() - ".class".length()) : className) - .replace("\\", "/") - .replace("/", "."); - if (norm.startsWith(getMixinPackage() + ".") && !norm.endsWith(".")) { - mixins.add(norm.substring(getMixinPackage().length() + 1)); - } - } - - /** - * Search through the JAR or class directory to find mixins contained in {@link #getMixinPackage()} - */ - @Override - public List getMixins() { - if (mixins != null) return mixins; - System.out.println("Trying to discover mixins"); - mixins = new ArrayList<>(); - URL classUrl = getClass().getProtectionDomain().getCodeSource().getLocation(); - System.out.println("Found classes at " + classUrl); - Path file; - try { - file = Paths.get(getBaseUrlForClassUrl(classUrl).toURI()); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - System.out.println("Base directory found at " + file); - if (Files.isDirectory(file)) { - walkDir(file); - } else { - walkJar(file); - } - System.out.println("Found mixins: " + mixins); - - return mixins; - } - - /** - * Search through directory for mixin classes based on {@link #getMixinBaseDir}. - * - * @param classRoot The root directory in which classes are stored for the default package. - */ - private void walkDir(Path classRoot) { - System.out.println("Trying to find mixins from directory"); - try (Stream classes = Files.walk(classRoot.resolve(getMixinBaseDir()))) { - classes.map(it -> classRoot.relativize(it).toString()) - .forEach(this::tryAddMixinClass); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Read through a JAR file, trying to find all mixins inside. - */ - private void walkJar(Path file) { - System.out.println("Trying to find mixins from jar file"); - try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(file))) { - ZipEntry next; - while ((next = zis.getNextEntry()) != null) { - tryAddMixinClass(next.getName()); - zis.closeEntry(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - - } - - @Override - public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - - } - - @Override - public String getRefMapperConfig() { - return null; - } - - @Override - public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - return true; - } - - @Override - public void acceptTargets(Set myTargets, Set otherTargets) { - - } -} diff --git a/src/main/java/de/torui/coflsky/listeners/ChatListener.java b/src/main/java/de/torui/coflsky/listeners/ChatListener.java index 1dda820..b808f5e 100644 --- a/src/main/java/de/torui/coflsky/listeners/ChatListener.java +++ b/src/main/java/de/torui/coflsky/listeners/ChatListener.java @@ -11,6 +11,11 @@ public class ChatListener { @SubscribeEvent(priority = EventPriority.LOW, receiveCanceled = true) public void onGuiChat(ClientChatReceivedEvent e) { String unformatted = ChatUtils.cleanColour(e.message.getUnformattedText()); + + if(unformatted.startsWith("Your new API key is ")){ + // We have found the api key YAY! + CoflSky.getAPIKeyManager().getApiInfo().key = unformatted.substring("Your new API key is ".length()).substring(0, 36); + } } diff --git a/src/main/java/de/torui/coflsky/listeners/OneConfigOpenListener.java b/src/main/java/de/torui/coflsky/listeners/OneConfigOpenListener.java new file mode 100644 index 0000000..4b50d0b --- /dev/null +++ b/src/main/java/de/torui/coflsky/listeners/OneConfigOpenListener.java @@ -0,0 +1,41 @@ +package de.torui.coflsky.listeners; + +import de.torui.coflsky.util.ServerSettingsLoader; +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +/** + * Listens for any GUI opened; if it is a OneConfig GUI, trigger remote settings load. + */ +public class OneConfigOpenListener { + + private static boolean guiSessionActive = false; // tracks a single OC GUI open chain + + private static boolean isOneConfigGui(GuiScreen gui) { + if (gui == null) return false; + String name = gui.getClass().getName(); + return name.startsWith("cc.polyfrost.oneconfig.gui") || name.contains("oneconfig"); + } + + @SubscribeEvent + public void onGuiOpen(GuiOpenEvent event) { + GuiScreen gui = event.gui; + // Reset session flag when OC GUI closed (gui == null) or a non-OC gui is opened + if (gui == null || !isOneConfigGui(gui)) { + guiSessionActive = false; + } + if (isOneConfigGui(gui)) { + if (guiSessionActive) { + return; // already handled in this session + } + guiSessionActive = true; + // Avoid duplicate fetches if one is already in progress or just applied + if (ServerSettingsLoader.isRunning() || ServerSettingsLoader.recentlyLoaded()) { + return; + } + // delay until GUI shown to ensure command handler available + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(ServerSettingsLoader::requestSettings); + } + } +} diff --git a/src/main/java/de/torui/coflsky/minecraft_integration/CoflSessionManager.java b/src/main/java/de/torui/coflsky/minecraft_integration/CoflSessionManager.java new file mode 100644 index 0000000..9a4edea --- /dev/null +++ b/src/main/java/de/torui/coflsky/minecraft_integration/CoflSessionManager.java @@ -0,0 +1,145 @@ +package de.torui.coflsky.minecraft_integration; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import net.minecraftforge.fml.common.Loader; + +public class CoflSessionManager { + public static Gson gson = new GsonBuilder() .registerTypeAdapter(ZonedDateTime.class, new TypeAdapter() { + @Override + public void write(JsonWriter out, ZonedDateTime value) throws IOException { + out.value(value.toString()); + } + + @Override + public ZonedDateTime read(JsonReader in) throws IOException { + return ZonedDateTime.parse(in.nextString()); + } + }) + .enableComplexMapKeySerialization().create(); + public static class CoflSession { + + public String SessionUUID; + public ZonedDateTime timestampCreated; + public CoflSession() {} + public CoflSession(String sessionUUID, ZonedDateTime timestampCreated) { + super(); + SessionUUID = sessionUUID; + this.timestampCreated = timestampCreated; + } + + } + + public static void UpdateCoflSessions() throws IOException { + Map sessions = GetCoflSessions(); + + for (String username : sessions.keySet()) { + if(!isValidSession(sessions.get(username))) { + DeleteCoflSession(username); + } + } + } + + public static Path GetTempFileFolder() { + + Path dataPath = Paths.get(Loader.instance().getConfigDir().getPath(), "CoflSky", "sessions"); + dataPath.toFile().mkdirs(); + + return dataPath; + } + + public static Map GetCoflSessions() throws IOException{ + + File[] sessions = GetTempFileFolder().toFile().listFiles(); + + Map map = new HashMap<>(); + + for (int i= 0; i JDK 7 + String resString = result.toString("UTF-8"); + + return resString; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return null; + } +} \ No newline at end of file diff --git a/src/main/java/de/torui/coflsky/network/WSClient.java b/src/main/java/de/torui/coflsky/network/WSClient.java new file mode 100644 index 0000000..6f42ba6 --- /dev/null +++ b/src/main/java/de/torui/coflsky/network/WSClient.java @@ -0,0 +1,144 @@ +package de.torui.coflsky.network; + +import java.io.IOException; +import java.net.URI; +import java.security.NoSuchAlgorithmException; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.neovisionaries.ws.client.WebSocket; +import com.neovisionaries.ws.client.WebSocketAdapter; +import com.neovisionaries.ws.client.WebSocketException; +import com.neovisionaries.ws.client.WebSocketFactory; +import com.neovisionaries.ws.client.WebSocketState; + +import de.torui.coflsky.CoflSky; +import de.torui.coflsky.WSCommandHandler; +import de.torui.coflsky.commands.Command; +import de.torui.coflsky.commands.JsonStringCommand; +import de.torui.coflsky.commands.RawCommand; +import net.minecraft.client.Minecraft; + +public class WSClient extends WebSocketAdapter { + + public static Gson gson; + + + static { + gson = new GsonBuilder()/*.setFieldNamingStrategy(new FieldNamingStrategy() { + @Override + public String translateName(Field f) { + + String name = f.getName(); + char firstChar = name.charAt(0); + return Character.toLowerCase(firstChar) + name.substring(1); + } + })*/.create(); + } + public URI uri; + private WebSocket socket; + public boolean shouldRun = false; + public WebSocketState currentState = WebSocketState.CLOSED; + + public WSClient(URI uri) { + this.uri = uri; + + } + + public synchronized void start() throws IOException, WebSocketException, NoSuchAlgorithmException { + WebSocketFactory factory = new WebSocketFactory(); + + /*// Create a custom SSL context. + SSLContext context = NaiveSSLContext.getInstance("TLS"); + + // Set the custom SSL context. + factory.setSSLContext(context); + + // Disable manual hostname verification for NaiveSSLContext. + // + // Manual hostname verification has been enabled since the + // version 2.1. Because the verification is executed manually + // after Socket.connect(SocketAddress, int) succeeds, the + // hostname verification is always executed even if you has + // passed an SSLContext which naively accepts any server + // certificate. However, this behavior is not desirable in + // some cases and you may want to disable the hostname + // verification. You can disable the hostname verification + // by calling WebSocketFactory.setVerifyHostname(false). + factory.setVerifyHostname(false); + factory.*/ + factory.setVerifyHostname(false); + factory.setSSLContext(NaiveSSLContext.getInstance("TLSv1.2")); + factory.setConnectionTimeout(5*1000); + this.socket = factory.createSocket(uri); + this.socket.addListener(this); + this.socket.connect(); + } + + public void stop() { + System.out.println("Closing Socket"); + if(socket == null) + return; + socket.clearListeners(); + socket.disconnect(); + System.out.println("Socket closed"); + + } + + @Override + public void onStateChanged(WebSocket websocket, WebSocketState newState) throws Exception { + System.out.println("WebSocket Changed state to: " + newState); + currentState = newState; + + boolean isActiveSocket = CoflSky.Wrapper.socket == this; + if(newState == WebSocketState.CLOSED && shouldRun && isActiveSocket) { + CoflSky.Wrapper.restartWebsocketConnection(); + } + if(!isActiveSocket){ + websocket.clearListeners(); + } + + super.onStateChanged(websocket, newState); + } + + + + @Override + public void onTextMessage(WebSocket websocket, String text) throws Exception{ + + //super.onTextMessage(websocket, text); + System.out.println("Received: "+ text); + JsonStringCommand cmd = gson.fromJson(text, JsonStringCommand.class); + //System.out.println(cmd); + try { + WSCommandHandler.HandleCommand(cmd, Minecraft.getMinecraft().thePlayer); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + public void SendCommand(Command cmd) { + SendCommand(new RawCommand(cmd.getType().ToJson(),gson.toJson(cmd.getData()))); + } + public void SendCommand(RawCommand cmd) { + Send(cmd); + } + + public synchronized void Send(Object obj) { + String json = gson.toJson(obj); + System.out.println("###Sending message of json value " + json); + if(this.socket == null) + try + { + start(); + } catch(Exception e) + { + System.out.println("Ran into an error on implicit start for send: "+ e); + } + this.socket.sendText(json); + } + + + +} \ No newline at end of file diff --git a/src/main/java/de/torui/coflsky/network/WSClientWrapper.java b/src/main/java/de/torui/coflsky/network/WSClientWrapper.java new file mode 100644 index 0000000..b1ab73b --- /dev/null +++ b/src/main/java/de/torui/coflsky/network/WSClientWrapper.java @@ -0,0 +1,173 @@ +package de.torui.coflsky.network; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; + +import com.neovisionaries.ws.client.WebSocketException; +import com.neovisionaries.ws.client.WebSocketState; + +import de.torui.coflsky.CoflSky; +import de.torui.coflsky.commands.Command; +import de.torui.coflsky.commands.JsonStringCommand; +import de.torui.coflsky.commands.RawCommand; +import de.torui.coflsky.minecraft_integration.PlayerDataProvider; +import net.minecraft.client.Minecraft; +import net.minecraft.event.ClickEvent; +import net.minecraft.event.ClickEvent.Action; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.ChatStyle; +import net.minecraft.util.EnumChatFormatting; +import de.torui.coflsky.minecraft_integration.CoflSessionManager; + + +public class WSClientWrapper { + public WSClient socket; + // public Thread thread; + public boolean isRunning; + + private String[] uris; + + + public WSClientWrapper(String[] uris) { + this.uris = uris; + } + + public void restartWebsocketConnection() { + socket.stop(); + + System.out.println("Sleeping..."); + Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Lost connection to Coflnet, trying to reestablish the connection in 2 Seconds...")); + + socket = new WSClient(socket.uri); + isRunning = false; + while(isRunning == false) { + start(); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + socket.shouldRun = true; + } + + + public boolean startConnection() { + if(isRunning) + return false; + + for(String s : uris) { + System.out.println("Trying connection with uri=" + s); + + if(initializeNewSocket(s)) { + return true; + } + } + + Minecraft.getMinecraft().thePlayer.addChatMessage( + new ChatComponentText("Cofl could not establish a connection to any server!\n" + + "If you use a vpn/proxy please try connecting without it.\n" + + "If that does not work please contact us on our ") + .setChatStyle(new ChatStyle().setColor(EnumChatFormatting.RED)) + .appendSibling(new ChatComponentText("discord!") + .setChatStyle(new ChatStyle() + .setColor(EnumChatFormatting.BLUE) + .setChatClickEvent(new ClickEvent(Action.OPEN_URL, "https://discord.gg/wvKXfTgCfb"))))); + + return false; + } + + + + public boolean initializeNewSocket(String uriPrefix) { + + + String uri = uriPrefix; + uri += "?version=" + CoflSky.VERSION; + + String username = PlayerDataProvider.getUsername(); + uri += "&player=" + username; + + //Generate a CoflSession + + try { + CoflSessionManager.UpdateCoflSessions(); + String coflSessionID = CoflSessionManager.GetCoflSession(username).SessionUUID; + + uri += "&SId=" + coflSessionID; + + if(socket != null) + socket.stop(); + socket = new WSClient(URI.create(uri)); + isRunning = false; + boolean successfull = start(); + if(successfull) { + socket.shouldRun = true; + } + return successfull; + } catch(IOException e) { + e.printStackTrace(); + } + + return false; + + } + + private boolean start() { + if(socket.currentState == WebSocketState.CLOSED) { + try { + socket.start(); + isRunning = true; + return true; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (WebSocketException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return false; + } else { + System.out.println("Status is " + socket.currentState); + } + return false; + } + + public synchronized void stop() { + if(isRunning) { + socket.shouldRun = false; + socket.stop(); + isRunning = false; + socket = null; + } + } + + public synchronized void SendMessage(RawCommand cmd){ + if(this.isRunning) { + this.socket.SendCommand(cmd); + } else { + Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("tried sending a callback to coflnet but failed. the connection must be closed.")); + } + } + public synchronized void SendMessage(Command cmd){ + if(this.isRunning) { + this.socket.SendCommand(cmd); + } else { + Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("tried sending a callback to coflnet but failed. the connection must be closed.")); + } + + } + + + public String GetStatus() { + return "" + isRunning + " " + + (this.socket!=null ? this.socket.currentState.toString() : "NOT_INITIALIZED"); + } +} diff --git a/src/main/java/de/torui/coflsky/proxy/APIKeyManager.java b/src/main/java/de/torui/coflsky/proxy/APIKeyManager.java new file mode 100644 index 0000000..e726552 --- /dev/null +++ b/src/main/java/de/torui/coflsky/proxy/APIKeyManager.java @@ -0,0 +1,54 @@ +package de.torui.coflsky.proxy; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import net.minecraftforge.fml.common.Loader; + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Collectors; + +public class APIKeyManager { + private final Gson gson = new Gson(); + private APIInfo apiInfo = new APIInfo(); + + public APIInfo getApiInfo(){ + return this.apiInfo; + } + + + public class APIInfo{ + @SerializedName("api-key") + public String key; + } + + public void loadIfExists() throws Exception { + Path dataPath = Paths.get(Loader.instance().getConfigDir().getPath(), "CoflSky", "api-key.json"); + File file = dataPath.toFile(); + if(file.exists()) { + BufferedReader reader = new BufferedReader( new InputStreamReader(new FileInputStream(file))); + String raw = reader.lines().collect(Collectors.joining("\n")); + this.apiInfo = gson.fromJson(raw,APIInfo.class); + reader.close(); + } + } + + + public void saveKey() throws Exception { + Path dataPath = Paths.get(Loader.instance().getConfigDir().getPath(), "CoflSky", "api-key.json"); + File file = dataPath.toFile(); + if(file.exists()) { + file.delete(); + } + file.createNewFile(); + + String data = gson.toJson(apiInfo); + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); + bw.append(data); + bw.flush(); + bw.close(); + } + + +} diff --git a/src/main/java/de/torui/coflsky/proxy/ProxyManager.java b/src/main/java/de/torui/coflsky/proxy/ProxyManager.java new file mode 100644 index 0000000..3a6b3ae --- /dev/null +++ b/src/main/java/de/torui/coflsky/proxy/ProxyManager.java @@ -0,0 +1,95 @@ +package de.torui.coflsky.proxy; + +import de.torui.coflsky.CoflSky; +import de.torui.coflsky.commands.models.ProxyRequest; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ProxyManager { + private final ExecutorService requestExecutor = Executors.newSingleThreadExecutor(); + + public void handleRequestAsync(ProxyRequest request) { + try { + CompletableFuture req = this.doRequest(request.getUrl()); + if (request.isUploadEnabled()) { + req.thenAcceptAsync(res -> this.uploadData(res, request.getId(), request.getUploadTo())); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private String getString(HttpURLConnection con) { + try { + InputStream in = new BufferedInputStream(con.getInputStream()); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + for (int length; (length = in.read(buffer)) != -1;) { + result.write(buffer, 0, length); + } + String resString = result.toString("UTF-8"); + return resString; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + public void uploadData(String data, String id, String uploadTo) { + this.requestExecutor.submit(new Runnable() { + @Override + public void run() { + try { + URL url = new URL(uploadTo); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("User-Agent", "CoflMod-157"); + con.setRequestProperty("X-Request-Id", id); + + con.setDoOutput(true); + con.setDoInput(true); + + OutputStream os = con.getOutputStream(); + os.write(data.getBytes("UTF-8")); + os.close(); + String response = getString(con); + System.out.println("Response=" + response); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + }); + } + + private CompletableFuture doRequest(String targetUrl) { + CompletableFuture future = new CompletableFuture<>(); + + this.requestExecutor.submit(new Runnable() { + @Override + public void run() { + try { + URL url = new URL(targetUrl); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("Accept", "application/json"); + con.setRequestProperty("User-Agent", "CoflMod"); + + String key = CoflSky.getAPIKeyManager().getApiInfo().key; + + con.setDoInput(true); + future.complete(getString(con)); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + }); + + return future; + } + +} diff --git a/src/main/java/de/torui/coflsky/util/ServerSettingsLoader.java b/src/main/java/de/torui/coflsky/util/ServerSettingsLoader.java new file mode 100644 index 0000000..79c5930 --- /dev/null +++ b/src/main/java/de/torui/coflsky/util/ServerSettingsLoader.java @@ -0,0 +1,452 @@ +package de.torui.coflsky.util; + +import cc.polyfrost.oneconfig.libs.universal.UChat; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.torui.coflsky.config.CoflConfig; +import de.torui.coflsky.config.SettingsCache; +import net.minecraft.client.Minecraft; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.*; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.layout.PatternLayout; + +/** + * Utility that, on request, executes the server command "/cofl get json", tails + * latest.log until the JSON blob is encountered, then applies those values to + * {@link CoflConfig} via reflection. + */ +public class ServerSettingsLoader { + private static final Path LOG_FILE = resolveLatestFile(); + private static final Pattern JSON_PATTERN = Pattern.compile("\\[CoflSettings] (\\{.*})"); + private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "CoflSettingsLoader"); + t.setDaemon(true); + return t; + }); + private static final AtomicBoolean RUNNING = new AtomicBoolean(false); + private static final AtomicBoolean APPLIED = new AtomicBoolean(false); // true once settings successfully applied + private static final AtomicBoolean LOADED_MSG_SHOWN = new AtomicBoolean(false); + private static final Gson GSON = new Gson(); + private static volatile boolean APPENDER_REGISTERED = false; + private static volatile String LAST_JSON_APPLIED = null; + private static final AtomicBoolean GUI_REFRESHED = new AtomicBoolean(false); + private static Runnable onLoadedCallback = null; + private static final long COOLDOWN_MS = 5000; // 5-second cooldown between loads + private static volatile long lastLoadTime = 0; + + private static Path resolveLatestFile() { + Path dir = Paths.get(System.getenv("APPDATA"), ".minecraft", "logs"); + String[] candidates = {"latest.log", "latest", "latest.txt"}; + for (String name : candidates) { + Path p = dir.resolve(name); + if (Files.exists(p)) return p; + } + return dir.resolve("latest.log"); // fallback + } + + /** + * Fire the command and begin listening for settings. If already running it is a no-op. + */ + public static void requestSettings() { + requestSettings(false); + } + + public static void requestSettingsSilent() { + requestSettings(true); + } + + private static void requestSettings(boolean silent) { + // cooldown logic: if recently loaded and not running, skip + if (!RUNNING.get() && (System.currentTimeMillis() - lastLoadTime) < COOLDOWN_MS) { + if (!silent) { + UChat.chat("&7[Cofl] &eSettings were just loaded. Please wait a moment..."); + } + return; + } + if (RUNNING.getAndSet(true)) return; + // register console listener once + if (!APPENDER_REGISTERED) { + registerConsoleListener(); + APPENDER_REGISTERED = true; + } + // Tell the user unless silent + if (!silent) { + UChat.chat("&7[Cofl] &eSettings loading..."); + } + // reset state flags for this request + APPLIED.set(false); + LOADED_MSG_SHOWN.set(false); + // schedule send next tick to ensure player is in proper state + Minecraft mc = Minecraft.getMinecraft(); + mc.addScheduledTask(() -> { + boolean handled = net.minecraftforge.client.ClientCommandHandler.instance.executeCommand(mc.thePlayer, "/cofl get json") != 0; + if (!handled) { + mc.thePlayer.sendChatMessage("/cofl get json"); + } + }); + // Start background tailer + EXECUTOR.submit(ServerSettingsLoader::tailerLoop); + } + + /** + * Request settings from server and invoke the supplied callback once they have been + * successfully applied on the client thread. + * + * @param onLoaded Callback executed exactly once after settings load or immediately if they are already fresh. + */ + public static void requestSettingsAndThen(Runnable onLoaded) { + // If a load is already running, just queue the callback normally + if (isRunning()) { + addPostLoadAction(onLoaded); + return; + } + // If settings are already fresh (within cooldown) invoke callback immediately + if (recentlyLoaded()) { + Minecraft.getMinecraft().addScheduledTask(onLoaded); + return; + } + // Otherwise trigger a fresh load and invoke later + onLoadedCallback = onLoaded; + requestSettings(); + } + + /** + * Register an action to be executed after the current (or next) settings load completes. + * + * @param r callback to execute + */ + public static void addPostLoadAction(Runnable r) { + if (onLoadedCallback == null) { + onLoadedCallback = r; + } else { + Runnable existing = onLoadedCallback; + onLoadedCallback = () -> { + existing.run(); + r.run(); + }; + } + } + + private static void tailerLoop() { + long start = System.currentTimeMillis(); + try (SeekableByteChannel ch = Files.newByteChannel(LOG_FILE, StandardOpenOption.READ)) { + ch.position(ch.size()); // jump to EOF + ByteBuffer buf = ByteBuffer.allocate(4096); + StringBuilder line = new StringBuilder(); + while (RUNNING.get()) { // exit once settings applied + // timeout after 30 seconds + if (System.currentTimeMillis() - start > 30_000) { + if (!APPLIED.get()) { + UChat.chat("&c[Cofl] Settings load timed out."); + } + RUNNING.set(false); + return; + } + int read = ch.read(buf); + if (read == 0) { + Thread.sleep(200); + continue; + } + buf.flip(); + while (buf.hasRemaining()) { + char c = (char) buf.get(); + if (c == '\n' || c == '\r') { + if (processLine(line.toString())) { + // successfully applied settings, stop loop + return; + } + line.setLength(0); + } else { + line.append(c); + } + } + buf.clear(); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + UChat.chat("&cFailed to load settings: " + e.getMessage()); + RUNNING.set(false); + } + } + + /** + * Processes a single console line for settings JSON. + * @param l line content + * @return true if settings were found and applied + */ + private static boolean processLine(String l) { + // First try tag pattern + Matcher m = JSON_PATTERN.matcher(l); + if (m.find()) { + applySettings(m.group(1)); + RUNNING.set(false); + return true; + } + // Fallback: if line contains "data=" pattern + int idx = l.indexOf("data="); + if (idx != -1) { + String json = l.substring(idx + 5).trim(); + applySettings(json); + RUNNING.set(false); + return true; + } + // Final fallback: raw JSON line starting with '{' or '[' + String t = l.trim(); + if (!t.isEmpty() && (t.startsWith("{") || t.startsWith("["))) { + applySettings(t); + RUNNING.set(false); + return true; + } + return false; + } + + public static void applySettings(String raw) { + String json = raw.trim(); + if (json.equals(LAST_JSON_APPLIED)) { + // Duplicate payload – treat as successful load to prevent timeout + APPLIED.set(true); + RUNNING.set(false); + // Invoke any pending callback on client thread + if (onLoadedCallback != null) { + Minecraft.getMinecraft().addScheduledTask(onLoadedCallback); + onLoadedCallback = null; + } + return; + } + // Remember whether this invocation was triggered by a manual "/cofl get json" request + final boolean fromRequestFlow = RUNNING.get(); + // Attempt to remove an extra trailing ']' common in Command toString + if ((json.startsWith("[") && json.endsWith("]]") )) { + json = json.substring(0, json.length() - 1); + } + JsonElement rootEl; + try { + rootEl = GSON.fromJson(json, JsonElement.class); + } catch (Exception ex) { + return; // parsing failed + } + if (rootEl == null) return; + + // mark as applied before mutating fields so timeout logic knows + APPLIED.set(true); + lastLoadTime = System.currentTimeMillis(); // record time of successful load for cooldown + + if (rootEl.isJsonArray()) { + for (JsonElement elem : rootEl.getAsJsonArray()) { + if (!elem.isJsonObject()) continue; + JsonObject obj = elem.getAsJsonObject(); + if (!obj.has("value")) continue; + String k = obj.has("key") ? obj.get("key").getAsString() : + (obj.has("name") ? obj.get("name").getAsString() : null); + if (k == null) continue; + setField(k, obj.get("value")); + } + } else if (rootEl.isJsonObject()) { + JsonObject root = rootEl.getAsJsonObject(); + if (root.has("value") && (root.has("key") || root.has("name"))) { + String k = root.has("key") ? root.get("key").getAsString() : root.get("name").getAsString(); + setField(k, root.get("value")); + } else { + for (Map.Entry e : root.entrySet()) { + setField(e.getKey(), e.getValue()); + } + } + } + // Force OneConfig to update UI and save + Minecraft.getMinecraft().addScheduledTask(() -> { + // Refresh GUI only if user currently has a OneConfig screen open + if (Minecraft.getMinecraft().currentScreen != null && + Minecraft.getMinecraft().currentScreen.getClass().getName().contains("oneconfig") && + !GUI_REFRESHED.get()) { + Minecraft.getMinecraft().displayGuiScreen(null); + de.torui.coflsky.config.CoflConfig.openGuiStatic(); + GUI_REFRESHED.set(true); + } + // Only announce success if this was part of a manual request flow + if (fromRequestFlow && LOADED_MSG_SHOWN.compareAndSet(false, true)) { + UChat.chat("&aSettings loaded!"); + } + // Invoke caller-supplied callback once after applying settings + if (onLoadedCallback != null) { + onLoadedCallback.run(); + onLoadedCallback = null; + } + }); + LAST_JSON_APPLIED = json; + RUNNING.set(false); + } + + private static void setField(String key, JsonElement valueEl) { + try { + Field f; + try { + f = CoflConfig.class.getDeclaredField(key); + } catch (NoSuchFieldException nf) { + f = null; + for (Field cand : CoflConfig.class.getDeclaredFields()) { + if (cand.getName().equalsIgnoreCase(key)) { + f = cand; + break; + } + } + if (f == null) { + // Store unknown key in cache for other consumers but don't error spam + try { + Object valObj = valueEl.isJsonNull() ? null : (valueEl.isJsonPrimitive() ? + valueEl.getAsString() : valueEl.toString()); + SettingsCache.put(key, valObj); + } catch (Throwable ignored) {} + return; + } + } + // store into cache instead of reflection (for now still set static for compat) + f.setAccessible(true); + Object parsedValue = parseElement(valueEl, f.getType()); + if (parsedValue == null) { + // store raw JSON string for reference but skip setting field to avoid exception spam + SettingsCache.put(key, valueEl.isJsonPrimitive() ? valueEl.getAsString() : valueEl.toString()); + return; + } + /* + * Write value under BOTH keys: + * 1. The raw key received from server (JSON) → lets CoflConfig.bool/int/... helpers that + * use the same spelling pick up the value. + * 2. The exact static field name (f.getName()) → preserves compatibility with any existing + * client-side code that still references the uppercase/camel variant directly. + */ + Object oldVal = SettingsCache.get(key); + SettingsCache.put(key, parsedValue); + if (!key.equals(f.getName())) { + SettingsCache.put(f.getName(), parsedValue); + } + try { f.set(null, parsedValue); } catch (Throwable ignored) {} + if (LAST_JSON_APPLIED != null && (oldVal == null || !oldVal.equals(parsedValue))) { + String typeNote = (parsedValue != null && !f.getType().isAssignableFrom(parsedValue.getClass())) ? " &6(coerced)" : ""; + UChat.chat("&7[Settings] " + key + " &e" + String.valueOf(oldVal) + " &7→ &a" + String.valueOf(parsedValue) + typeNote); + } + } catch (Exception ex) { + UChat.chat("&c[Settings] Failed to set " + key + ": " + ex.getMessage()); + } + } + + private static Object parseElement(JsonElement el, Class target) { + if (target == int.class || target == Integer.class) { + return el.isJsonPrimitive() && el.getAsJsonPrimitive().isString() ? + Integer.parseInt(el.getAsString()) : el.getAsInt(); + } else if (target == boolean.class || target == Boolean.class) { + if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) { + return Boolean.parseBoolean(el.getAsString()); + } + return el.getAsBoolean(); + } else if (target == float.class || target == Float.class) { + return el.isJsonPrimitive() && el.getAsJsonPrimitive().isString() ? + Float.parseFloat(el.getAsString()) : el.getAsFloat(); + } else if (target == double.class || target == Double.class) { + return el.isJsonPrimitive() && el.getAsJsonPrimitive().isString() ? + Double.parseDouble(el.getAsString()) : el.getAsDouble(); + } else if (target == byte.class || target == Byte.class) { + return el.isJsonPrimitive() ? el.getAsByte() : null; + } else if (target == short.class || target == Short.class) { + return el.isJsonPrimitive() ? el.getAsShort() : null; + } else if (target == long.class || target == Long.class) { + return el.isJsonPrimitive() && el.getAsJsonPrimitive().isString() ? + Long.parseLong(el.getAsString()) : el.getAsLong(); + } else if (target.isEnum()) { + try { + String s = el.getAsString(); + if (s == null) return null; + @SuppressWarnings({"rawtypes","unchecked"}) + Object ev = Enum.valueOf((Class) target, s.toUpperCase()); + return ev; + } catch (Exception ignored) { return null; } + } else if (target == String.class) { + if (el.isJsonNull()) return null; + if (el.isJsonPrimitive()) return el.getAsString(); + return el.toString(); + } + // Fallback: attempt numeric coercion (e.g., server sent double but field is int) + try { + if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isNumber()) { + Number n = el.getAsNumber(); + if (target == int.class || target == Integer.class) { + return n.intValue(); + } else if (target == long.class || target == Long.class) { + return n.longValue(); + } else if (target == float.class || target == Float.class) { + return n.floatValue(); + } else if (target == double.class || target == Double.class) { + return n.doubleValue(); + } else if (target == byte.class || target == Byte.class) { + return n.byteValue(); + } else if (target == short.class || target == Short.class) { + return n.shortValue(); + } + } + } catch (Throwable ignored) {} + return null; + } + + /** Attach a Log4j2 appender to intercept console lines for settings JSON. */ + private static void registerConsoleListener() { + Logger root = (Logger) LogManager.getRootLogger(); + Appender app = new AbstractAppender("CoflSettingsConsole", null, null) { + @Override + public void append(LogEvent event) { + String msg = event.getMessage().getFormattedMessage(); + // Try original tag based pattern first + Matcher m = JSON_PATTERN.matcher(msg); + if (m.find()) { + applySettings(m.group(1)); + return; // avoid logging recursion + } else if (msg.contains("data=")) { + int idx = msg.indexOf("data=") + 5; + String json = msg.substring(idx).trim(); + applySettings(json); + return; + } else if (msg.toLowerCase().contains("updated")) { + // Heuristic: server indicates a single setting changed; fetch full set again. + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> { + if (!isRunning()) { + requestSettingsSilent(); // silent auto-refresh + } + }); + } + } + }; + app.start(); + root.addAppender(app); + } + + /** Returns true while a settings load request is currently in progress. */ + public static boolean isRunning() { + return RUNNING.get(); + } + + /** Returns true when the last settings request has completed and been applied. */ + public static boolean isUpToDate() { + return APPLIED.get(); + } + + /** Returns true if settings were loaded very recently (within cooldown window). */ + public static boolean recentlyLoaded() { + return (System.currentTimeMillis() - lastLoadTime) < COOLDOWN_MS; + } +} diff --git a/src/main/resources/accesstransformer.cfg b/src/main/resources/accesstransformer.cfg deleted file mode 100644 index 54e1549..0000000 --- a/src/main/resources/accesstransformer.cfg +++ /dev/null @@ -1,4 +0,0 @@ - -public net.minecraft.client.gui.inventory.GuiContainer field_147003_i # guiLeft -public net.minecraft.client.gui.inventory.GuiContainer field_147009_r # guiTop -public net.minecraft.client.gui.inventory.GuiEditSign field_146848_a # tileSign \ No newline at end of file diff --git a/src/main/resources/mixins.skycofl.json b/src/main/resources/mixins.skycofl.json deleted file mode 100644 index 25eae57..0000000 --- a/src/main/resources/mixins.skycofl.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "package": "${basePackage}.mixins", - "plugin": "${basePackage}.init.AutoDiscoveryMixinPlugin", - "refmap": "mixins.${modid}.refmap.json", - "minVersion": "0.7", - "compatibilityLevel": "JAVA_8", - "__comment": "You do not need to manually register mixins in this template. Check the auto discovery mixin plugin for more info." -}