feat: Refactor Pokémon loading to include empty slot checks and add a new README file.
This commit is contained in:
130
README.md
Normal file
130
README.md
Normal 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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user