From f58899edadeaa905c489cacb660838b38cb553cf Mon Sep 17 00:00:00 2001 From: GamerBoss101 Date: Thu, 5 Jun 2025 14:13:24 -0400 Subject: [PATCH] SyncMon Structure --- build.gradle | 18 ++- .../kotlin/co/sirblob/CobbleSyncClient.kt | 19 +-- .../kotlin/co/sirblob/KeybindImports.kt | 0 src/main/kotlin/co/sirblob/CobbleSync.kt | 149 ++++++++++++++++-- src/main/kotlin/co/sirblob/HTTPException.kt | 4 + src/main/kotlin/co/sirblob/Request.kt | 137 ++++++++++++++++ src/main/kotlin/co/sirblob/SyncMon.kt | 64 ++++++++ 7 files changed, 352 insertions(+), 39 deletions(-) create mode 100644 src/client/kotlin/co/sirblob/KeybindImports.kt create mode 100644 src/main/kotlin/co/sirblob/HTTPException.kt create mode 100644 src/main/kotlin/co/sirblob/Request.kt create mode 100644 src/main/kotlin/co/sirblob/SyncMon.kt diff --git a/build.gradle b/build.gradle index c960c2b..ca92552 100644 --- a/build.gradle +++ b/build.gradle @@ -46,16 +46,18 @@ fabricApi { } dependencies { - // To change the versions see the gradle.properties file - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings loom.officialMojangMappings() - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings loom.officialMojangMappings() + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}" + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}" - modImplementation "com.cobblemon:fabric:1.6.0+1.21.1-SNAPSHOT" + modImplementation "com.cobblemon:fabric:1.6.0+1.21.1-SNAPSHOT" + implementation "com.google.code.gson:gson:2.10.1" + implementation "org.json:json:20231013" // Or the latest version } processResources { diff --git a/src/client/kotlin/co/sirblob/CobbleSyncClient.kt b/src/client/kotlin/co/sirblob/CobbleSyncClient.kt index 6a9d439..a16921a 100644 --- a/src/client/kotlin/co/sirblob/CobbleSyncClient.kt +++ b/src/client/kotlin/co/sirblob/CobbleSyncClient.kt @@ -1,18 +1,11 @@ package co.sirblob -import org.lwjgl.glfw.GLFW -import org.lwjgl.system.windows.KEYBDINPUT - -import net.minecraft.client.KeyMapping -import net.minecraft.network.chat.Component - -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper import com.mojang.blaze3d.platform.InputConstants - -import com.cobblemon.mod.common.api.* -import com.cobblemon.mod.common.api.storage.PokemonStoreManager +import net.fabricmc.api.ClientModInitializer +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper +import net.minecraft.client.KeyMapping +import org.lwjgl.glfw.GLFW object CobbleSyncClient : ClientModInitializer { @@ -41,8 +34,6 @@ object CobbleSyncClient : ClientModInitializer { if(player != null) { - - } } diff --git a/src/client/kotlin/co/sirblob/KeybindImports.kt b/src/client/kotlin/co/sirblob/KeybindImports.kt new file mode 100644 index 0000000..e69de29 diff --git a/src/main/kotlin/co/sirblob/CobbleSync.kt b/src/main/kotlin/co/sirblob/CobbleSync.kt index 4f5d8b8..3d9dfa3 100644 --- a/src/main/kotlin/co/sirblob/CobbleSync.kt +++ b/src/main/kotlin/co/sirblob/CobbleSync.kt @@ -1,22 +1,60 @@ package co.sirblob +import com.cobblemon.mod.common.api.text.red +import com.cobblemon.mod.common.api.pokemon.PokemonSpecies +import com.cobblemon.mod.common.api.pokemon.stats.Stat +import com.cobblemon.mod.common.api.pokemon.stats.Stats +import com.cobblemon.mod.common.util.pc +import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.pokemon.Species +import com.cobblemon.mod.common.Cobblemon +import org.json.JSONObject +import com.mojang.brigadier.Command +import com.mojang.brigadier.arguments.IntegerArgumentType +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.context.CommandContext +import com.google.gson.JsonObject import net.fabricmc.api.ModInitializer import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback import net.minecraft.commands.CommandSourceStack import net.minecraft.network.chat.Component +import net.minecraft.core.RegistryAccess +import net.minecraft.resources.ResourceLocation import org.slf4j.LoggerFactory -import com.mojang.brigadier.builder.LiteralArgumentBuilder -import com.mojang.brigadier.context.CommandContext -import com.mojang.brigadier.Command -import com.cobblemon.mod.common.api.text.Text - +import SyncMon object CobbleSync : ModInitializer { - private val logger = LoggerFactory.getLogger("cobblesync") + + private val logger = LoggerFactory.getLogger("cobblesync") + private val request = Request("http://localhost:5173") + + + private fun Pokemon.toSyncJSONObject(): JSONObject { + val statsJson = JSONObject() + .put("hp", this.maxHealth) + .put("attack", this.attack) + .put("defense", this.defence) + .put("specialAttack", this.specialAttack) + .put("specialDefense", this.specialDefence) + .put("speed", this.speed) + + return JSONObject() + .put("species", this.species.name) + .put("level", this.level) + .put("nickname", this.nickname ?: "") + .put("shiny", this.shiny) + .put("stats", statsJson) + .put("ivs", this.ivs.map { (stat, value) -> + JSONObject().put(stat.toString(), value) + }) + .put("friendship", this.friendship) + .put("nature", this.nature.toString()) + .put("ability", this.ability) + } + override fun onInitialize() { - CommandRegistrationCallback.EVENT.register(CommandRegistrationCallback { dispatcher, _, _ -> dispatcher.register( LiteralArgumentBuilder.literal("cobblesync") @@ -24,18 +62,95 @@ object CobbleSync : ModInitializer { LiteralArgumentBuilder.literal("sync") .executes(Command { context: CommandContext -> - var player = context.source.player + val player = context.source.playerOrException - player?.sendSystemMessage( - Component.literal("CobbleSync is not yet implemented. Please check back later.") - .withStyle { style -> - style.withColor(0xFF0000) - } - ) + val pc = player.pc() - 1 - }) - ) + player.sendSystemMessage(Component.literal("Syncing box...").red()) + + val box30 = pc.boxes.get(29) + + var pokemonArry = ArrayList() + var pokemonCount = 0 + + box30.filterNotNull().forEach { pokemon -> + pokemonArry.add(pokemon) + logger.info("Found pokemon: ${pokemon.species.name}") + pokemonCount++ + } + + if (pokemonCount < 1) { + player.sendSystemMessage(Component.literal("Box 30 is empty!").red()) + return@Command 1 + } else if (pokemonCount > 12) { + player.sendSystemMessage(Component.literal("Box 30 has too many pokemon!").red()) + return@Command 1 + } + + try { + + val pokemonJsonList = pokemonArry.map { pokemon -> + pokemon.toSyncJSONObject() + } + + val payload = JSONObject() + .put("pokemon", pokemonJsonList) + .put("count", pokemonCount) + + var response = request.POST("/api/cobblesync/" + player.uuid.toString(), payload) + + logger.info(response.toString()) + + if (response.getInt("status") != 200) { + player.sendSystemMessage(Component.literal("Failed to sync box 30!").red()) + return@Command 1 + } + + // Clear Box 30 before adding new Pokémon + // for (i in 0 until box30.count()) { + // box30.set(i, null) + // } + + val newPokemonsArray = response.optJSONArray("pokemon") + if (newPokemonsArray == null) { + player.sendSystemMessage(Component.literal("No Pokémon data received in server response.").red()) + // Box is now empty, which might be the intended state if server sends no pokemon + return@Command 1 + } + + // Iterate through the received Pokémon and add them to Box 30 in an open slot + + for (i in 0 until newPokemonsArray.length()) { + val pokemonJson = newPokemonsArray.getJSONObject(i) + val newPokemon = SyncMon().createFromJSON(pokemonJson) + + // Find the first empty slot in Box 30 + + logger.info(newPokemon.toSyncJSONObject().toString()) + + for (j in 0 until box30.count()) { + if (box30.get(j) == null) { + box30.pc.add(newPokemon) + logger.info("Added ${newPokemon.species.name} to Box 30 at slot $j") + break + } + } + } + + } catch (e: HTTPException) { + logger.error("HTTP Exception: ${e.message}") + player.sendSystemMessage(Component.literal("Error syncing box 30!").red()) + return@Command 1 + } catch (e: Exception) { + logger.error("Exception: ${e.message}") + player.sendSystemMessage(Component.literal("An unexpected error occurred!").red()) + return@Command 1 + } + + player.sendSystemMessage(Component.literal("Box 30 synced successfully!").red()) + 1 + }) + ) ) }) diff --git a/src/main/kotlin/co/sirblob/HTTPException.kt b/src/main/kotlin/co/sirblob/HTTPException.kt new file mode 100644 index 0000000..9827ef2 --- /dev/null +++ b/src/main/kotlin/co/sirblob/HTTPException.kt @@ -0,0 +1,4 @@ +package co.sirblob + + +class HTTPException(val responseCode: Int, message: String) : Exception(message) \ No newline at end of file diff --git a/src/main/kotlin/co/sirblob/Request.kt b/src/main/kotlin/co/sirblob/Request.kt new file mode 100644 index 0000000..92b0135 --- /dev/null +++ b/src/main/kotlin/co/sirblob/Request.kt @@ -0,0 +1,137 @@ +package co.sirblob + + +import java.io.BufferedReader +import java.io.DataOutputStream +import java.io.IOException +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URI +import java.net.URL +import java.nio.charset.StandardCharsets +import org.json.JSONObject + +class Request(baseUrl: String) { + + var baseUrl: String = baseUrl + private set + + private val headers: MutableMap = mutableMapOf() + + init { + this.addHeader("Accept", "*/*") + this.addHeader("Content-Type", "application/json; charset=utf-8") + } + + fun setBaseUrl(url: String) { + this.baseUrl = url + } + + fun addHeader(key: String, value: String) { + this.headers[key] = value + } + + fun setHeader(key: String, value: String) { + this.headers[key] = value + } + + fun removeHeader(key: String) { + this.headers.remove(key) + } + + @Throws(HTTPException::class) + private fun generateResponse(responseCode: Int, responseBody: String): JSONObject { + return when (responseCode) { + HttpURLConnection.HTTP_OK -> { + if (responseBody.startsWith("{") || responseBody.startsWith("[")) { + JSONObject(responseBody).put("status", responseCode) + } else if (responseBody.isBlank()) { + JSONObject().put("status", responseCode) + } else { + JSONObject().put("data", responseBody).put("status", responseCode) + } + } + HttpURLConnection.HTTP_NO_CONTENT -> JSONObject().put("status", responseCode) + HttpURLConnection.HTTP_BAD_REQUEST -> throw HTTPException(responseCode, "Bad request") + HttpURLConnection.HTTP_UNAUTHORIZED -> throw HTTPException(responseCode, "Unauthorized") + HttpURLConnection.HTTP_FORBIDDEN -> throw HTTPException(responseCode, "Forbidden") + HttpURLConnection.HTTP_NOT_FOUND -> throw HTTPException(responseCode, "Not found") + HttpURLConnection.HTTP_INTERNAL_ERROR -> throw HTTPException(responseCode, "Internal server error") + else -> { + if (!responseBody.startsWith("{") && !responseBody.startsWith("[") && responseBody.isNotBlank()) { + JSONObject().put("data", responseBody).put("status", responseCode) + } else if (responseBody.startsWith("{") || responseBody.startsWith("[")) { + JSONObject(responseBody).put("status", responseCode) + } + else { + JSONObject().put("status", responseCode) + } + } + } + } + + @Throws(IOException::class, HTTPException::class) + private fun performRequest(urlPath: String, method: String, requestBodyJson: JSONObject? = null): JSONObject { + val fullUrl = URI.create(baseUrl).resolve(urlPath).toURL() + val con = fullUrl.openConnection() as HttpURLConnection + try { + con.requestMethod = method + headers.forEach { (key, value) -> con.setRequestProperty(key, value) } + + con.connectTimeout = 5000 + con.readTimeout = 5000 + + if (requestBodyJson != null && (method == "POST" || method == "PUT" || method == "PATCH")) { + con.doOutput = true + val bodyBytes = requestBodyJson.toString().toByteArray(StandardCharsets.UTF_8) + con.setRequestProperty("Content-Length", bodyBytes.size.toString()) + DataOutputStream(con.outputStream).use { outputStream -> + outputStream.write(bodyBytes) + outputStream.flush() + } + } + + val responseCode = con.responseCode + + val responseBody: String = try { + val streamToRead = if (responseCode < HttpURLConnection.HTTP_BAD_REQUEST) { + con.inputStream + } else { + con.errorStream + } + streamToRead?.bufferedReader(StandardCharsets.UTF_8)?.use { it.readText() } ?: "" + } catch (e: IOException) { + "" + } + + return generateResponse(responseCode, responseBody) + } finally { + con.disconnect() + } + } + + @Throws(IOException::class, HTTPException::class) + fun GET(urlPath: String): JSONObject { + return performRequest(urlPath, "GET") + } + + @Throws(IOException::class, HTTPException::class) + fun POST(urlPath: String, body: JSONObject): JSONObject { + return performRequest(urlPath, "POST", body) + } + + @Throws(IOException::class, HTTPException::class) + fun PUT(urlPath: String, body: JSONObject): JSONObject { + return performRequest(urlPath, "PUT", body) + } + + @Throws(IOException::class, HTTPException::class) + fun PATCH(urlPath: String, body: JSONObject): JSONObject { + return performRequest(urlPath, "PATCH", body) + } + + @Throws(IOException::class, HTTPException::class) + fun DELETE(urlPath: String): JSONObject { + return performRequest(urlPath, "DELETE") + } +} \ No newline at end of file diff --git a/src/main/kotlin/co/sirblob/SyncMon.kt b/src/main/kotlin/co/sirblob/SyncMon.kt new file mode 100644 index 0000000..e188362 --- /dev/null +++ b/src/main/kotlin/co/sirblob/SyncMon.kt @@ -0,0 +1,64 @@ + +import com.cobblemon.mod.common.api.pokemon.PokemonSpecies +import com.cobblemon.mod.common.api.pokemon.stats.Stats +import com.cobblemon.mod.common.api.pokemon.Natures +import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.pokemon.Nature +import org.json.JSONObject +import net.minecraft.network.chat.Component + + +class SyncMon: Pokemon { + + constructor() : super() + + public fun toSyncJSONObject(): JSONObject { + val statsJson = JSONObject() + .put("hp", this.maxHealth) + .put("attack", this.attack) + .put("defense", this.defence) + .put("specialAttack", this.specialAttack) + .put("specialDefense", this.specialDefence) + .put("speed", this.speed) + + return JSONObject() + .put("species", this.species.name) + .put("level", this.level) + .put("nickname", this.nickname ?: "") + .put("shiny", this.shiny) + .put("stats", statsJson) + .put("ivs", this.ivs.map { (stat, value) -> + JSONObject().put(stat.toString(), value) + }) + .put("friendship", this.friendship) + .put("nature", this.nature.toString()) + .put("ability", this.ability) + } + + public fun createFromJSON(json: JSONObject): Pokemon { + val speciesName = json.getString("species") + val species = PokemonSpecies.getByName(speciesName.lowercase()) ?: throw IllegalArgumentException("Unknown species: $speciesName") + + val pokemon = Pokemon() + pokemon.species = species + pokemon.level = json.getInt("level") + pokemon.shiny = json.getBoolean("shiny") + pokemon.setFriendship(json.getInt("friendship")) + super.nickname = Component.literal(json.getString("nickname")) + pokemon.nature = Natures.getNature(json.getString("nature")) ?: Natures.GENTLE + + //val statsJson = json.getJSONObject("stats") + + val ivsJson = json.getJSONArray("ivs") + for (i in 0 until ivsJson.length()) { + val ivJson = ivsJson.getJSONObject(i) + for (key in ivJson.keys()) { + val stat = Stats.getStat(key) + pokemon.setIV(stat, ivJson.getInt(key)) + } + } + + return pokemon + } + +} \ No newline at end of file