Cobblesync GUI and Packet Handler Update

This commit is contained in:
2025-12-12 21:18:44 +00:00
parent dd78f89164
commit 1d9a551350
14 changed files with 693 additions and 173 deletions

View File

@@ -47,14 +47,14 @@ dependencies {
modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}" modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}"
// Other Mod Dependencies (e.g., Cobblemon) // Other Mod Dependencies (e.g., Cobblemon)
modImplementation "com.cobblemon:fabric:1.6.0+1.21.1-SNAPSHOT" modImplementation "com.cobblemon:fabric:1.7.2+1.21.1-SNAPSHOT"
// JSON and GSON dependencies - add implementation first, then include // JSON and GSON dependencies - add implementation first, then include
implementation "org.json:json:20231013" implementation "org.json:json:20250517"
include "org.json:json:20231013" include "org.json:json:20250517"
implementation "com.google.code.gson:gson:2.10.1" implementation "com.google.code.gson:gson:2.13.2"
include "com.google.code.gson:gson:2.10.1" include "com.google.code.gson:gson:2.13.2"
} }
processResources { processResources {

View File

@@ -1,19 +1,19 @@
# Done to increase the memory available to gradle. # Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G org.gradle.jvmargs=-Xmx2G
org.gradle.parallel=true org.gradle.parallel=true
# Fabric Properties # Fabric Properties
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.1 minecraft_version=1.21.1
loader_version=0.16.14 loader_version=0.17.2
loom_version=1.10-SNAPSHOT loom_version=1.11-SNAPSHOT
fabric_kotlin_version=1.13.2+kotlin.2.1.20 fabric_kotlin_version=1.13.6+kotlin.2.2.20
# Mod Properties # Mod Properties
mod_version=1.0.0 mod_version=1.5.0
maven_group=co.sirblob maven_group=co.sirblob
archives_base_name=cobblesync archives_base_name=cobblesync
# Dependencies # Dependencies
fabric_version=0.116.0+1.21.1 fabric_version=0.116.6+1.21.1

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

9
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -205,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped. # and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.

3
gradlew.bat vendored
View File

@@ -70,11 +70,10 @@ goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

View File

@@ -1,42 +1,49 @@
package co.sirblob package co.sirblob
import co.sirblob.gui.CobbleSyncScreen
import co.sirblob.network.ClientPacketHandler
import com.mojang.blaze3d.platform.InputConstants import com.mojang.blaze3d.platform.InputConstants
import net.fabricmc.api.ClientModInitializer import net.fabricmc.api.ClientModInitializer
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper
import net.minecraft.client.KeyMapping import net.minecraft.client.KeyMapping
import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW
import org.slf4j.LoggerFactory
object CobbleSyncClient : ClientModInitializer { object CobbleSyncClient : ClientModInitializer {
override fun onInitializeClient() { private val logger = LoggerFactory.getLogger("cobblesync-client")
val binding1 = KeyBindingHelper.registerKeyBinding( override fun onInitializeClient() {
KeyMapping( logger.info("CobbleSync Client initializing...")
"key.fabric-key-binding-api-v1-testmod.test_keybinding_1",
InputConstants.Type.KEYSYM,
GLFW.GLFW_KEY_U,
"key.category.first.test"
)
)
ClientTickEvents.END_CLIENT_TICK.register { client -> // Register client-side network handlers
while (binding1.consumeClick()) { ClientPacketHandler.register()
var player = client.player // Register keybind for opening the CobbleSync GUI
val openGuiKeybind =
// client.player?.sendSystemMessage( KeyBindingHelper.registerKeyBinding(
// Component.literal("Key 1 was pressed!") KeyMapping(
// .withStyle { style -> "key.cobblesync.open_gui",
// style.withColor(0x00FF00) InputConstants.Type.KEYSYM,
// } GLFW.GLFW_KEY_U,
// ) "key.categories.cobblesync"
)
)
if(player != null) { // Handle keybind press
ClientTickEvents.END_CLIENT_TICK.register { client ->
while (openGuiKeybind.consumeClick()) {
val player = client.player
if (player != null && client.screen == null) {
// Open the CobbleSync GUI
client.setScreen(CobbleSyncScreen())
logger.info("Opened CobbleSync GUI")
} }
} }
} }
}
} logger.info("CobbleSync Client initialized!")
}
}

View File

@@ -0,0 +1,151 @@
package co.sirblob.gui
import co.sirblob.network.CobbleSyncPackets
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
@Environment(EnvType.CLIENT)
class CobbleSyncScreen : Screen(Component.literal("CobbleSync")) {
// Status message to display feedback
private var statusMessage: Component = Component.literal("")
private var statusColor: Int = 0xFFFFFF
// Buttons
private lateinit var syncButton: Button
private lateinit var loadButton: Button
private lateinit var closeButton: Button
override fun init() {
super.init()
val buttonWidth = 200
val buttonHeight = 20
val centerX = width / 2
val centerY = height / 2
// Sync Button - uploads Box 30 to the server
syncButton = Button.builder(Component.literal("⬆ Sync Box 30")) { _ ->
setStatus(Component.literal("Syncing..."), 0xFFFF55)
ClientPlayNetworking.send(CobbleSyncPackets.SyncRequestPayload())
}
.bounds(centerX - buttonWidth / 2, centerY - 40, buttonWidth, buttonHeight)
.build()
// Load Button - downloads saved Pokemon to Box 1
loadButton = Button.builder(Component.literal("⬇ Load to Box 1")) { _ ->
setStatus(Component.literal("Loading..."), 0xFFFF55)
ClientPlayNetworking.send(CobbleSyncPackets.LoadRequestPayload())
}
.bounds(centerX - buttonWidth / 2, centerY - 10, buttonWidth, buttonHeight)
.build()
// Close Button
closeButton = Button.builder(Component.literal("Close")) { _ ->
onClose()
}
.bounds(centerX - buttonWidth / 2, centerY + 30, buttonWidth, buttonHeight)
.build()
addRenderableWidget(syncButton)
addRenderableWidget(loadButton)
addRenderableWidget(closeButton)
}
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
// Render background
renderBackground(guiGraphics, mouseX, mouseY, partialTick)
// Draw title
guiGraphics.drawCenteredString(
font,
Component.literal("§6§lCobbleSync"),
width / 2,
height / 2 - 80,
0xFFFFFF
)
// Draw subtitle/description
guiGraphics.drawCenteredString(
font,
Component.literal("§7Sync your Pokémon across servers"),
width / 2,
height / 2 - 65,
0xAAAAAA
)
// Draw separator line
guiGraphics.fill(
width / 2 - 100,
height / 2 - 55,
width / 2 + 100,
height / 2 - 54,
0xFF444444.toInt()
)
// Draw status message
if (statusMessage.string.isNotEmpty()) {
guiGraphics.drawCenteredString(
font,
statusMessage,
width / 2,
height / 2 + 60,
statusColor
)
}
// Draw info text
guiGraphics.drawCenteredString(
font,
Component.literal("§8Sync: Upload Box 30 (max 12 Pokémon)"),
width / 2,
height / 2 + 80,
0x888888
)
guiGraphics.drawCenteredString(
font,
Component.literal("§8Load: Download to Box 1 (must be empty)"),
width / 2,
height / 2 + 92,
0x888888
)
super.render(guiGraphics, mouseX, mouseY, partialTick)
}
override fun isPauseScreen(): Boolean = false
fun setStatus(message: Component, color: Int) {
this.statusMessage = message
this.statusColor = color
}
companion object {
// Singleton instance for updating status from network handlers
var currentInstance: CobbleSyncScreen? = null
private set
fun open(screen: CobbleSyncScreen) {
currentInstance = screen
}
fun close() {
currentInstance = null
}
}
override fun onClose() {
CobbleSyncScreen.close()
super.onClose()
}
override fun added() {
super.added()
CobbleSyncScreen.open(this)
}
}

View File

@@ -0,0 +1,27 @@
package co.sirblob.network
import co.sirblob.gui.CobbleSyncScreen
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.minecraft.network.chat.Component
/**
* Client-side packet handlers for CobbleSync
*/
@Environment(EnvType.CLIENT)
object ClientPacketHandler {
fun register() {
// Register handler for sync response
ClientPlayNetworking.registerGlobalReceiver(CobbleSyncPackets.SYNC_RESPONSE_ID) { payload, context ->
context.client().execute {
val screen = CobbleSyncScreen.currentInstance
if (screen != null) {
val color = if (payload.success) 0x55FF55 else 0xFF5555
screen.setStatus(Component.literal(payload.message), color)
}
}
}
}
}

View File

@@ -1,158 +1,283 @@
package co.sirblob package co.sirblob
import com.cobblemon.mod.common.api.text.red import co.sirblob.network.ServerPacketHandler
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.blue import com.cobblemon.mod.common.api.text.blue
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.pokemon.stats.Stat import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.storage.pc.PCBox
import com.cobblemon.mod.common.util.pc 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 com.cobblemon.mod.relocations.oracle.truffle.regex.tregex.util.json.Json
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 com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import com.mojang.brigadier.Command
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.context.CommandContext
import net.fabricmc.api.ModInitializer import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback
import net.minecraft.commands.CommandSourceStack import net.minecraft.commands.CommandSourceStack
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.core.RegistryAccess import org.json.JSONObject
import net.minecraft.resources.ResourceLocation
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
object CobbleSync : ModInitializer { object CobbleSync : ModInitializer {
private val logger = LoggerFactory.getLogger("cobblesync") private val logger = LoggerFactory.getLogger("cobblesync")
private val request = Request("https://authserver.sirblob.co") private val request = Request("https://authserver.sirblob.co")
override fun onInitialize() { override fun onInitialize() {
logger.info("CobbleSync initializing...")
CommandRegistrationCallback.EVENT.register(CommandRegistrationCallback { dispatcher, _, _ -> // Register network packet handlers for GUI communication
dispatcher.register( ServerPacketHandler.register()
LiteralArgumentBuilder.literal<CommandSourceStack>("cobblesync") logger.info("Registered CobbleSync network handlers")
.then(
LiteralArgumentBuilder.literal<CommandSourceStack>("sync")
.executes(Command<CommandSourceStack> { context: CommandContext<CommandSourceStack> ->
val player = context.source.playerOrException
player.sendSystemMessage(Component.literal("Syncing box...").red())
val box30 = player.pc().boxes.get(29)
var pokemonCount = 0
box30.filterNotNull().forEach { pokemon -> CommandRegistrationCallback.EVENT.register(
player.sendSystemMessage( CommandRegistrationCallback { dispatcher, _, _ ->
Component.literal("Syncing Pokémon: ${pokemon.species.name} (Level ${pokemon.level})") dispatcher.register(
.blue() LiteralArgumentBuilder.literal<CommandSourceStack>("cobblesync")
.then(
LiteralArgumentBuilder.literal<CommandSourceStack>(
"sync"
)
.executes(
Command<CommandSourceStack> {
context:
CommandContext<
CommandSourceStack>
->
val player =
context.source
.playerOrException
player.sendSystemMessage(
Component.literal(
"Syncing box..."
)
.red()
)
val box30 =
player.pc().boxes.get(29)
var pokemonCount = 0
box30.filterNotNull().forEach {
pokemon ->
player.sendSystemMessage(
Component.literal(
"Syncing Pokémon: ${pokemon.species.name} (Level ${pokemon.level})"
)
.blue()
)
pokemonCount++
}
if (pokemonCount < 1) {
player.sendSystemMessage(
Component.literal(
"[Sync Failed] Box 1 is empty!"
)
.red()
)
return@Command 1
} else if (pokemonCount > 12) {
player.sendSystemMessage(
Component.literal(
"[Sync Failed] Box 1 has too many pokemon!"
)
.red()
)
return@Command 1
}
var obj =
box30.saveToJSON(
JsonObject(),
player.registryAccess()
)
val payload =
JSONObject()
.put(
"pokemon",
obj.toString()
)
.put(
"count",
pokemonCount
)
logger.info(
"/api/cobblesync/" +
player.uuid
.toString()
)
try {
var response =
request.POST(
"/api/cobblesync/" +
player.uuid
.toString(),
payload
)
logger.info(response.toString())
player.sendSystemMessage(
Component.literal(
"Box 30 synced successfully!"
)
.green()
)
} 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
}
1
}
)
) )
pokemonCount++ .then(
} LiteralArgumentBuilder.literal<CommandSourceStack>(
"load"
)
.executes(
Command<CommandSourceStack> {
context:
CommandContext<
CommandSourceStack>
->
val player =
context.source
.playerOrException
if (pokemonCount < 1) { val pc = player.pc()
player.sendSystemMessage(Component.literal("[Sync Failed] Box 1 is empty!").red()) player.sendSystemMessage(
return@Command 1 Component.literal(
} else if (pokemonCount > 12) { "Syncing box..."
player.sendSystemMessage(Component.literal("[Sync Failed] Box 1 has too many pokemon!").red()) )
return@Command 1 .green()
} )
var obj = box30.saveToJSON(JsonObject(), player.registryAccess()) var box1 = pc.boxes.get(0)
val payload = JSONObject() var pokemonCount = 0
.put("pokemon", obj.toString()) box1.pc.forEach({ _ ->
.put("count", pokemonCount) pokemonCount++
})
logger.info("/api/cobblesync/" + player.uuid.toString()) if (pokemonCount > 0) {
player.sendSystemMessage(
Component.literal(
"[Load Failed] Box 1 is not empty!"
)
.red()
)
return@Command 1
}
try { try {
var response = request.POST("/api/cobblesync/" + player.uuid.toString(), payload)
logger.info(response.toString()) var response =
request.GET(
"/api/cobblesync/" +
player.uuid
.toString()
)
logger.info(response.toString())
player.sendSystemMessage(Component.literal("Box 30 synced successfully!").green()) if (response.getInt("status") !=
200
) {
player.sendSystemMessage(
Component.literal(
"Failed to load box 1!"
)
.red()
)
return@Command 1
}
} catch (e: HTTPException) { var obj =
logger.error("HTTP Exception: ${e.message}") JsonParser.parseString(
player.sendSystemMessage(Component.literal("Error syncing box 30!").red()) response.getString(
return@Command 1 "pokemon"
} catch (e: Exception) { )
logger.error("Exception: ${e.message}") )
player.sendSystemMessage(Component.literal("An unexpected error occurred!").red()) .asJsonObject
return@Command 1
}
1 var newBox =
}) box1.loadFromJSON(
obj,
player.registryAccess()
)
newBox.pc.filterNotNull()
.forEach { pokemon ->
player.sendSystemMessage(
Component
.literal(
"Received Pokémon: ${pokemon.species.name} (Level ${pokemon.level})"
)
.blue()
)
box1.pc.add(pokemon)
}
player.sendSystemMessage(
Component.literal(
"Box 1 loaded successfully!"
)
.green()
)
} catch (e: HTTPException) {
logger.error(
"HTTP Exception: ${e.message}"
)
player.sendSystemMessage(
Component.literal(
"Error loading box 1!"
)
.red()
)
return@Command 1
} catch (e: Exception) {
logger.error(
"Exception: ${e.message}"
)
player.sendSystemMessage(
Component.literal(
"An unexpected error occurred!"
)
.red()
)
return@Command 1
}
1
}
)
)
) )
.then( }
LiteralArgumentBuilder.literal<CommandSourceStack>("load") )
.executes(Command<CommandSourceStack> { context: CommandContext<CommandSourceStack> ->
val player = context.source.playerOrException
val pc = player.pc()
player.sendSystemMessage(Component.literal("Syncing box...").green())
var box1 = pc.boxes.get(0)
var pokemonCount = 0
box1.pc.forEach({ _ ->
pokemonCount++
})
if (pokemonCount > 0) {
player.sendSystemMessage(Component.literal("[Load Failed] Box 1 is not empty!").red())
return@Command 1
}
try {
var response = request.GET("/api/cobblesync/" + player.uuid.toString())
logger.info(response.toString())
if (response.getInt("status") != 200) {
player.sendSystemMessage(Component.literal("Failed to load box 1!").red())
return@Command 1
}
var obj = JsonParser.parseString(response.getString("pokemon")).asJsonObject
var newBox = box1.loadFromJSON(obj, player.registryAccess())
newBox.pc.filterNotNull().forEach { pokemon ->
player.sendSystemMessage(
Component.literal("Received Pokémon: ${pokemon.species.name} (Level ${pokemon.level})")
.blue()
)
box1.pc.add(pokemon)
}
player.sendSystemMessage(Component.literal("Box 1 loaded successfully!").green())
} catch (e: HTTPException) {
logger.error("HTTP Exception: ${e.message}")
player.sendSystemMessage(Component.literal("Error loading box 1!").red())
return@Command 1
} catch (e: Exception) {
logger.error("Exception: ${e.message}")
player.sendSystemMessage(Component.literal("An unexpected error occurred!").red())
return@Command 1
}
1
}
)
))
})
} }
} }

View File

@@ -0,0 +1,75 @@
package co.sirblob.network
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import net.minecraft.network.protocol.common.custom.CustomPacketPayload
import net.minecraft.resources.ResourceLocation
/**
* Network packet definitions for CobbleSync client-server communication
*/
object CobbleSyncPackets {
// Packet IDs
val SYNC_REQUEST_ID = CustomPacketPayload.Type<SyncRequestPayload>(
ResourceLocation.fromNamespaceAndPath("cobblesync", "sync_request")
)
val LOAD_REQUEST_ID = CustomPacketPayload.Type<LoadRequestPayload>(
ResourceLocation.fromNamespaceAndPath("cobblesync", "load_request")
)
val SYNC_RESPONSE_ID = CustomPacketPayload.Type<SyncResponsePayload>(
ResourceLocation.fromNamespaceAndPath("cobblesync", "sync_response")
)
/**
* Client -> Server: Request to sync Box 30
*/
class SyncRequestPayload : CustomPacketPayload {
override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> = SYNC_REQUEST_ID
companion object {
val STREAM_CODEC: StreamCodec<FriendlyByteBuf, SyncRequestPayload> = StreamCodec.of(
{ _, _ -> }, // encode - nothing to write
{ _ -> SyncRequestPayload() } // decode - just create instance
)
}
}
/**
* Client -> Server: Request to load Pokemon to Box 1
*/
class LoadRequestPayload : CustomPacketPayload {
override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> = LOAD_REQUEST_ID
companion object {
val STREAM_CODEC: StreamCodec<FriendlyByteBuf, LoadRequestPayload> = StreamCodec.of(
{ _, _ -> },
{ _ -> LoadRequestPayload() }
)
}
}
/**
* Server -> Client: Response with status message
*/
class SyncResponsePayload(
val success: Boolean,
val message: String
) : CustomPacketPayload {
override fun type(): CustomPacketPayload.Type<out CustomPacketPayload> = SYNC_RESPONSE_ID
companion object {
val STREAM_CODEC: StreamCodec<FriendlyByteBuf, SyncResponsePayload> = StreamCodec.of(
{ buf, payload ->
buf.writeBoolean(payload.success)
buf.writeUtf(payload.message)
},
{ buf ->
val success = buf.readBoolean()
val message = buf.readUtf()
SyncResponsePayload(success, message)
}
)
}
}
}

View File

@@ -0,0 +1,135 @@
package co.sirblob.network
import co.sirblob.HTTPException
import co.sirblob.Request
import com.cobblemon.mod.common.util.pc
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.minecraft.server.level.ServerPlayer
import org.json.JSONObject
import org.slf4j.LoggerFactory
/** Server-side packet handlers for CobbleSync operations */
object ServerPacketHandler {
private val logger = LoggerFactory.getLogger("cobblesync")
private val request = Request("https://authserver.sirblob.co")
fun register() {
// Register packet types
PayloadTypeRegistry.playC2S()
.register(
CobbleSyncPackets.SYNC_REQUEST_ID,
CobbleSyncPackets.SyncRequestPayload.STREAM_CODEC
)
PayloadTypeRegistry.playC2S()
.register(
CobbleSyncPackets.LOAD_REQUEST_ID,
CobbleSyncPackets.LoadRequestPayload.STREAM_CODEC
)
PayloadTypeRegistry.playS2C()
.register(
CobbleSyncPackets.SYNC_RESPONSE_ID,
CobbleSyncPackets.SyncResponsePayload.STREAM_CODEC
)
// Register handlers
ServerPlayNetworking.registerGlobalReceiver(CobbleSyncPackets.SYNC_REQUEST_ID) { _, context
->
val player = context.player()
context.server().execute { handleSyncRequest(player) }
}
ServerPlayNetworking.registerGlobalReceiver(CobbleSyncPackets.LOAD_REQUEST_ID) { _, context
->
val player = context.player()
context.server().execute { handleLoadRequest(player) }
}
}
private fun handleSyncRequest(player: ServerPlayer) {
try {
val box30 = player.pc().boxes[29]
var pokemonCount = 0
box30.filterNotNull().forEach { pokemon ->
logger.info("Syncing Pokémon: ${pokemon.species.name} (Level ${pokemon.level})")
pokemonCount++
}
if (pokemonCount < 1) {
sendResponse(player, false, "Box 30 is empty!")
return
}
if (pokemonCount > 12) {
sendResponse(player, false, "Box 30 has too many Pokémon! (Max 12)")
return
}
val obj = box30.saveToJSON(JsonObject(), player.registryAccess())
val payload = JSONObject().put("pokemon", obj.toString()).put("count", pokemonCount)
logger.info("/api/cobblesync/${player.uuid}")
val response = request.POST("/api/cobblesync/${player.uuid}", payload)
logger.info(response.toString())
sendResponse(player, true, "Successfully synced $pokemonCount Pokémon!")
} catch (e: HTTPException) {
logger.error("HTTP Exception: ${e.message}")
sendResponse(player, false, "Server error: ${e.message}")
} catch (e: Exception) {
logger.error("Exception: ${e.message}")
sendResponse(player, false, "An unexpected error occurred!")
}
}
private fun handleLoadRequest(player: ServerPlayer) {
try {
val pc = player.pc()
val box1 = pc.boxes[0]
var pokemonCount = 0
box1.filterNotNull().forEach { _ -> pokemonCount++ }
if (pokemonCount > 0) {
sendResponse(player, false, "Box 1 is not empty!")
return
}
val response = request.GET("/api/cobblesync/${player.uuid}")
logger.info(response.toString())
if (response.getInt("status") != 200) {
sendResponse(player, false, "No saved Pokémon found!")
return
}
val obj = JsonParser.parseString(response.getString("pokemon")).asJsonObject
val newBox = box1.loadFromJSON(obj, player.registryAccess())
var loadedCount = 0
newBox.pc.filterNotNull().forEach { pokemon ->
logger.info("Loading Pokémon: ${pokemon.species.name} (Level ${pokemon.level})")
box1.pc.add(pokemon)
loadedCount++
}
sendResponse(player, true, "Successfully loaded $loadedCount Pokémon!")
} catch (e: HTTPException) {
logger.error("HTTP Exception: ${e.message}")
sendResponse(player, false, "Server error: ${e.message}")
} catch (e: Exception) {
logger.error("Exception: ${e.message}")
sendResponse(player, false, "An unexpected error occurred!")
}
}
private fun sendResponse(player: ServerPlayer, success: Boolean, message: String) {
ServerPlayNetworking.send(player, CobbleSyncPackets.SyncResponsePayload(success, message))
}
}

View File

@@ -0,0 +1,4 @@
{
"key.cobblesync.open_gui": "Open CobbleSync",
"key.categories.cobblesync": "CobbleSync"
}

View File

@@ -3,13 +3,13 @@
"id": "cobblesync", "id": "cobblesync",
"version": "${version}", "version": "${version}",
"name": "CobbleSync", "name": "CobbleSync",
"description": "This is an example description! Tell everyone what your mod is about!", "description": "Sync your Pokémon between servers!",
"authors": [ "authors": [
"Sir_Blob_" "Sir_Blob_"
], ],
"contact": { "contact": {
"homepage": "https://fabricmc.net/", "homepage": "https://fabricmc.net/",
"sources": "https://github.com/FabricMC/fabric-example-mod" "sources": "https://git.sirblob.co/GMMC/Cobblesync"
}, },
"license": "CC0-1.0", "license": "CC0-1.0",
"icon": "assets/cobblesync/icon.png", "icon": "assets/cobblesync/icon.png",