feat: Refactor Pokémon loading to include empty slot checks and add a new README file.

This commit is contained in:
2025-12-12 23:23:49 +00:00
parent 1d9a551350
commit 88c155d239
4 changed files with 312 additions and 101 deletions

130
README.md Normal file
View File

@@ -0,0 +1,130 @@
# CobbleSync
<p align="center">
<img src="src/main/resources/assets/cobblesync/icon.png" alt="CobbleSync Logo" width="128" height="128">
</p>
<p align="center">
<strong>Sync your Pokémon between servers!</strong>
</p>
<p align="center">
<a href="https://git.sirblob.co/GMMC/Cobblesync">
<img src="https://img.shields.io/badge/Minecraft-1.21.1-green.svg" alt="Minecraft Version">
</a>
<a href="https://fabricmc.net/">
<img src="https://img.shields.io/badge/Mod%20Loader-Fabric-blue.svg" alt="Fabric">
</a>
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-CC0--1.0-lightgrey.svg" alt="License">
</a>
</p>
---
## 📖 Description
CobbleSync is a Fabric mod for [Cobblemon](https://cobblemon.com/) that allows players to sync their Pokémon across different servers. Upload your Pokémon from one server and download them on another!
## ✨ Features
- **GUI Interface** - Easy-to-use graphical interface accessible with a keybind
- **Command Support** - Full command-line control for advanced users
- **Cross-Server Sync** - Transfer Pokémon between different Cobblemon servers
- **Safe Transfers** - Loads Pokémon to empty slots without overwriting existing ones
## 🎮 How to Use
### GUI Method (Recommended)
1. Press **U** (default keybind) to open the CobbleSync GUI
2. Click **"⬆ Sync Box 30"** to upload Pokémon from Box 30
3. On another server, click **"⬇ Load Pokémon"** to download your Pokémon to empty slots
### Command Method
| Command | Description |
|---------|-------------|
| `/cobblesync sync` | Upload Pokémon from Box 30 to the cloud |
| `/cobblesync load` | Download Pokémon to empty PC slots |
## ⚠️ Important Notes
- **Sync (Upload)**: Uses **Box 30** - Maximum of 12 Pokémon
- **Load (Download)**: Adds Pokémon to **empty slots** across all PC boxes (won't overwrite existing Pokémon)
- The keybind can be changed in Minecraft's Controls settings under "CobbleSync"
## 📦 Installation
### Requirements
- Minecraft **1.21.1**
- [Fabric Loader](https://fabricmc.net/) **0.16.14+**
- [Fabric API](https://modrinth.com/mod/fabric-api)
- [Fabric Language Kotlin](https://modrinth.com/mod/fabric-language-kotlin)
- [Cobblemon](https://cobblemon.com/) **1.7.2+**
### Steps
1. Download the latest release from the [Releases](https://git.sirblob.co/GMMC/Cobblesync/releases) page
2. Place the `.jar` file in your `mods` folder
3. Make sure all dependencies are installed
4. Launch Minecraft with Fabric
## 🛠️ Building from Source
```bash
# Clone the repository
git clone https://git.sirblob.co/GMMC/Cobblesync.git
cd Cobblesync
# Build the mod
./gradlew build
# The compiled JAR will be in build/libs/
```
## 📁 Project Structure
```
src/
├── client/kotlin/co/sirblob/
│ ├── CobbleSyncClient.kt # Client entry point
│ ├── gui/
│ │ └── CobbleSyncScreen.kt # GUI implementation
│ └── network/
│ └── ClientPacketHandler.kt
├── main/kotlin/co/sirblob/
│ ├── CobbleSync.kt # Main mod entry point
│ ├── Request.kt # HTTP client
│ ├── HTTPException.kt
│ └── network/
│ ├── CobbleSyncPackets.kt # Network packet definitions
│ └── ServerPacketHandler.kt
└── main/resources/
├── fabric.mod.json
└── assets/cobblesync/
├── icon.png
└── lang/en_us.json
```
## 🔧 Configuration
The keybind for opening the GUI can be configured in:
**Options → Controls → Key Binds → CobbleSync → Open CobbleSync**
Default: `U`
## 📝 License
This project is licensed under the [CC0-1.0 License](LICENSE).
## 👤 Author
**Sir_Blob_**
---
<p align="center">
Made with ❤️ for the GMMC
</p>

View File

@@ -30,27 +30,28 @@ class CobbleSyncScreen : Screen(Component.literal("CobbleSync")) {
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()
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()
// Load Button - downloads saved Pokemon to empty slots
loadButton =
Button.builder(Component.literal("Load Pokémon")) { _ ->
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()
closeButton =
Button.builder(Component.literal("Close")) { _ -> onClose() }
.bounds(centerX - buttonWidth / 2, centerY + 30, buttonWidth, buttonHeight)
.build()
addRenderableWidget(syncButton)
addRenderableWidget(loadButton)
@@ -63,56 +64,56 @@ class CobbleSyncScreen : Screen(Component.literal("CobbleSync")) {
// Draw title
guiGraphics.drawCenteredString(
font,
Component.literal("§6§lCobbleSync"),
width / 2,
height / 2 - 80,
0xFFFFFF
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
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()
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
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
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
font,
Component.literal("§8Load: Download to empty PC slots"),
width / 2,
height / 2 + 92,
0x888888
)
super.render(guiGraphics, mouseX, mouseY, partialTick)

View File

@@ -171,30 +171,12 @@ object CobbleSync : ModInitializer {
val pc = player.pc()
player.sendSystemMessage(
Component.literal(
"Syncing box..."
"Loading Pokémon..."
)
.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/" +
@@ -208,7 +190,7 @@ object CobbleSync : ModInitializer {
) {
player.sendSystemMessage(
Component.literal(
"Failed to load box 1!"
"No saved Pokémon found!"
)
.red()
)
@@ -223,28 +205,94 @@ object CobbleSync : ModInitializer {
)
.asJsonObject
var newBox =
box1.loadFromJSON(
obj,
player.registryAccess()
)
// Use a
// temporary
// box to
// parse the
// JSON
val tempBox =
pc.boxes[0]
.loadFromJSON(
obj,
player.registryAccess()
)
val pokemonToLoad =
tempBox.pc
.filterNotNull()
.toMutableList()
newBox.pc.filterNotNull()
.forEach { pokemon ->
player.sendSystemMessage(
Component
.literal(
"Received Pokémon: ${pokemon.species.name} (Level ${pokemon.level})"
)
.blue()
)
if (pokemonToLoad.isEmpty()) {
player.sendSystemMessage(
Component.literal(
"No Pokémon to load!"
)
.red()
)
return@Command 1
}
box1.pc.add(pokemon)
// Count
// available
// empty
// slots
var emptySlots = 0
for (box in pc.boxes) {
for (slot in 0 until 30) {
if (box[slot] == null) {
emptySlots++
}
}
}
if (emptySlots <
pokemonToLoad
.size
) {
player.sendSystemMessage(
Component.literal(
"Not enough empty slots! Need ${pokemonToLoad.size}, have $emptySlots"
)
.red()
)
return@Command 1
}
// Add
// Pokemon
// to empty
// slots
// across
// all boxes
var loadedCount = 0
for (pokemon in pokemonToLoad) {
var placed = false
for (box in pc.boxes) {
if (placed) break
for (slot in
0 until 30) {
if (box[slot] ==
null
) {
box[slot] =
pokemon
player.sendSystemMessage(
Component
.literal(
"Received Pokémon: ${pokemon.species.name} (Level ${pokemon.level})"
)
.blue()
)
loadedCount++
placed = true
break
}
}
}
}
player.sendSystemMessage(
Component.literal(
"Box 1 loaded successfully!"
"Successfully loaded $loadedCount Pokémon to empty slots!"
)
.green()
)
@@ -254,7 +302,7 @@ object CobbleSync : ModInitializer {
)
player.sendSystemMessage(
Component.literal(
"Error loading box 1!"
"Error loading Pokémon!"
)
.red()
)

View File

@@ -91,16 +91,8 @@ object ServerPacketHandler {
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
}
// Fetch synced Pokemon from the server
val response = request.GET("/api/cobblesync/${player.uuid}")
logger.info(response.toString())
@@ -110,16 +102,56 @@ object ServerPacketHandler {
}
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++
// Use a temporary box to parse the JSON data
val tempBox = pc.boxes[0].loadFromJSON(obj, player.registryAccess())
val pokemonToLoad = tempBox.pc.filterNotNull().toMutableList()
if (pokemonToLoad.isEmpty()) {
sendResponse(player, false, "No Pokémon to load!")
return
}
sendResponse(player, true, "Successfully loaded $loadedCount Pokémon!")
// Count available empty slots across all boxes
var emptySlots = 0
for (box in pc.boxes) {
for (slot in 0 until 30) { // Each box has 30 slots
if (box[slot] == null) {
emptySlots++
}
}
}
if (emptySlots < pokemonToLoad.size) {
sendResponse(
player,
false,
"Not enough empty slots! Need ${pokemonToLoad.size}, have $emptySlots"
)
return
}
// Add Pokemon to empty slots across all boxes
var loadedCount = 0
for (pokemon in pokemonToLoad) {
var placed = false
for (box in pc.boxes) {
if (placed) break
for (slot in 0 until 30) {
if (box[slot] == null) {
box[slot] = pokemon
logger.info(
"Loaded Pokémon: ${pokemon.species.name} (Level ${pokemon.level}) to slot $slot"
)
loadedCount++
placed = true
break
}
}
}
}
sendResponse(player, true, "Successfully loaded $loadedCount Pokémon to empty slots!")
} catch (e: HTTPException) {
logger.error("HTTP Exception: ${e.message}")
sendResponse(player, false, "Server error: ${e.message}")