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}"
// 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
implementation "org.json:json:20231013"
include "org.json:json:20231013"
implementation "org.json:json:20250517"
include "org.json:json:20250517"
implementation "com.google.code.gson:gson:2.10.1"
include "com.google.code.gson:gson:2.10.1"
implementation "com.google.code.gson:gson:2.13.2"
include "com.google.code.gson:gson:2.13.2"
}
processResources {

View File

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

9
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# 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
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -205,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# 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.
# * 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.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

3
gradlew.bat vendored
View File

@@ -70,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@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
@rem End local scope for the variables with windows NT shell

View File

@@ -1,42 +1,49 @@
package co.sirblob
import co.sirblob.gui.CobbleSyncScreen
import co.sirblob.network.ClientPacketHandler
import com.mojang.blaze3d.platform.InputConstants
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
import org.slf4j.LoggerFactory
object CobbleSyncClient : ClientModInitializer {
override fun onInitializeClient() {
private val logger = LoggerFactory.getLogger("cobblesync-client")
val binding1 = KeyBindingHelper.registerKeyBinding(
KeyMapping(
"key.fabric-key-binding-api-v1-testmod.test_keybinding_1",
InputConstants.Type.KEYSYM,
GLFW.GLFW_KEY_U,
"key.category.first.test"
)
)
override fun onInitializeClient() {
logger.info("CobbleSync Client initializing...")
ClientTickEvents.END_CLIENT_TICK.register { client ->
while (binding1.consumeClick()) {
// Register client-side network handlers
ClientPacketHandler.register()
var player = client.player
// client.player?.sendSystemMessage(
// Component.literal("Key 1 was pressed!")
// .withStyle { style ->
// style.withColor(0x00FF00)
// }
// )
// Register keybind for opening the CobbleSync GUI
val openGuiKeybind =
KeyBindingHelper.registerKeyBinding(
KeyMapping(
"key.cobblesync.open_gui",
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
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.green
import co.sirblob.network.ServerPacketHandler
import com.cobblemon.mod.common.api.text.blue
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.api.storage.pc.PCBox
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
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.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.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.json.JSONObject
import org.slf4j.LoggerFactory
object CobbleSync : ModInitializer {
private val logger = LoggerFactory.getLogger("cobblesync")
private val logger = LoggerFactory.getLogger("cobblesync")
private val request = Request("https://authserver.sirblob.co")
override fun onInitialize() {
logger.info("CobbleSync initializing...")
CommandRegistrationCallback.EVENT.register(CommandRegistrationCallback { dispatcher, _, _ ->
dispatcher.register(
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
// Register network packet handlers for GUI communication
ServerPacketHandler.register()
logger.info("Registered CobbleSync network handlers")
box30.filterNotNull().forEach { pokemon ->
player.sendSystemMessage(
Component.literal("Syncing Pokémon: ${pokemon.species.name} (Level ${pokemon.level})")
.blue()
CommandRegistrationCallback.EVENT.register(
CommandRegistrationCallback { dispatcher, _, _ ->
dispatcher.register(
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) {
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
}
val pc = player.pc()
player.sendSystemMessage(
Component.literal(
"Syncing box..."
)
.green()
)
var obj = box30.saveToJSON(JsonObject(), player.registryAccess())
var box1 = pc.boxes.get(0)
val payload = JSONObject()
.put("pokemon", obj.toString())
.put("count", pokemonCount)
var pokemonCount = 0
box1.pc.forEach({ _ ->
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 {
var response = request.POST("/api/cobblesync/" + player.uuid.toString(), payload)
try {
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) {
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
}
var obj =
JsonParser.parseString(
response.getString(
"pokemon"
)
)
.asJsonObject
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",
"version": "${version}",
"name": "CobbleSync",
"description": "This is an example description! Tell everyone what your mod is about!",
"description": "Sync your Pokémon between servers!",
"authors": [
"Sir_Blob_"
],
"contact": {
"homepage": "https://fabricmc.net/",
"sources": "https://github.com/FabricMC/fabric-example-mod"
"sources": "https://git.sirblob.co/GMMC/Cobblesync"
},
"license": "CC0-1.0",
"icon": "assets/cobblesync/icon.png",