Description
# Eclipse Core Client
A client-side companion mod for Cobblemon that enables advanced multi-player battle formats, including cooperative raid battles.
## Features
- **Multi-Player Raid Support** - Enables 1-4 player cooperative battles against a single powerful opponent
- **Targeting System Override** - Allows proper target selection in non-standard battle formats
- **Battle Side Organization** - Correctly displays allies and enemies in the battle UI
- **Doubles/Triples Gimmick Fix** - Prevents using the same battle gimmick (Mega Evolution, Terastallization, Z-Moves, Dynamax) multiple times in a single turn
## Requirements
- Minecraft 1.21.1
- Fabric Loader 0.16.5+
- Fabric API
- Fabric Language Kotlin
- Cobblemon 1.7.1+
## Installation
1. Download and place in your client's `mods` folder
2. This mod is **client-side only** - it communicates with compatible server mods via custom packets
## For Mod Developers
Interested in creating your own raid or multi-battle mod for Cobblemon? Here's what you need to know:
### The Core Challenge
Cobblemon's battle system is designed for 1v1 and 2v2 formats. To create raids (4v1, 3v1, etc.), you need to solve several problems:
1. **Battle Creation** - How do you start a battle with 4 players on one side?
2. **Targeting** - How do players target the single boss when adjacency rules say they can't?
3. **Side Display** - How do you show allies correctly when the client expects standard formats?
4. **Showdown Compatibility** - How do you make Showdown accept non-standard team configurations?
### Server-Side: Starting Multi-Player Battles
Look at Cobblemon's `BattleBuilder` and `BattleRegistry`. Key classes:
- `BattleBuilder` - Creates battles with actors and format
- `BattleActor` - Represents a participant (player or NPC)
- `BattleFormat` - Defines the battle rules (singles, doubles, etc.)
- `PokemonBattle` - The active battle instance
For raids, you'll need multiple `BattleActor` instances on one side. Study how Cobblemon handles doubles - it's the foundation for multi-player.
### The Showdown Layer
Cobblemon uses Pokemon Showdown for battle logic. Battles are created via:
```
BattleRegistry.startBattle(format, side1Actors, side2Actors)
```
Showdown receives team data as "pack strings" - compact representations of Pokemon. The format is documented in Showdown's source. Key insight: Showdown's `fPokemon` format includes species, level, moves, ability, etc.
For raid bosses, consider:
- How would you give a Pokemon more effective HP?
- How would you let one Pokemon take hits from multiple attackers?
- What happens when Showdown calculates damage against a "scaled" target?
### Client-Side: The Targeting Problem
In standard doubles, Pokemon can only target adjacent slots. In a 4v1 raid, ALL players need to target the single boss.
The client uses `ActiveClientBattlePokemon.getAdjacent()` to determine valid targets. This method checks positions and returns valid targets based on battle format.
Eclipse Core Client solves this by:
- Intercepting target requests
- Overriding adjacency calculations for raid formats
- Communicating raid configuration via custom packets
### Packet Communication
Fabric networking allows custom packets between server and client:
```kotlin
// Server-side: Register and send
PayloadTypeRegistry.playS2C().register(...)
ServerPlayNetworking.send(player, payload)
// Client-side: Register receiver
ClientPlayNetworking.registerGlobalReceiver(...)
```
Your packet should tell the client:
- Is this a raid battle?
- How many players are participating?
- Which side is the boss on?
The client needs this info BEFORE the battle UI renders to correctly organize sides.
### Showdown Modifications (Advanced)
Cobblemon extracts Showdown to `./showdown/`. The JavaScript files in `sim/` control battle mechanics:
- `battle.js` - Main battle loop and turn handling
- `pokemon.js` - Pokemon state, HP, PP management
- `side.js` - Side management and slot handling
- `battle-actions.js` - Move execution and damage
For raid mechanics, you might need to modify how:
- Damage is calculated against the boss
- PP is tracked (infinite PP for bosses?)
- Multi-target moves resolve
- The boss takes actions against multiple targets
Mixin into `GraalShowdownUnbundler.attemptUnbundle()` to patch files after extraction.
### Mixin Targets Reference
**Client-side** (for targeting/display fixes):
- `ActiveClientBattlePokemon` - Target selection and adjacency
- `BattleMoveSelection` - Move selection UI
- `ClientBattle` - Side organization
**Server-side** (for battle creation):
- `BattleBuilder` - Battle initialization
- `BattleActor` - Actor behavior
- `NPCBattleActor` - NPC-specific logic
### Getting Started
1. Create a basic server mod that starts a doubles battle
2. Add a third player to one side - observe what breaks
3. Study the errors to understand the constraints
4. Implement fixes one layer at a time (Showdown → Server → Client)
The journey from "doubles work" to "4v1 raids work" will teach you everything about Cobblemon's battle system.
---
## Eclipse Packet Protocol
Want your server mod to work with Eclipse Core Client? Implement these packets:
### Packet 1: Client Handshake (Client → Server)
**Channel:** `eclipse_raids:client_handshake`
When a player with Eclipse Core Client joins your server, they send this packet.
| Field | Type | Description |
|-------|------|-------------|
| protocolVersion | Int | Client protocol version (currently `2`) |
**Kotlin Codec:**
```kotlin
buf.writeInt(protocolVersion)
// Read: buf.readInt()
```
Use this to track which players have the client mod installed. Block raid participation for players without it.
### Packet 2: Handshake Acknowledgment (Server → Client)
**Channel:** `eclipse_raids:handshake_ack`
Send this back to confirm the client is recognized.
| Field | Type | Description |
|-------|------|-------------|
| raidsEnabled | Boolean | Whether raids are enabled on this server |
| message | String | Status message (shown in client logs) |
**Kotlin Codec:**
```kotlin
buf.writeBoolean(raidsEnabled)
buf.writeString(message)
```
### Packet 3: Raid Side Configuration (Server → Client)
**Channel:** `eclipse_raids:raid_side_config`
**This is the critical packet.** Send this after starting a raid battle to tell the client which actors are allies vs enemies.
| Field | Type | Description |
|-------|------|-------------|
| battleId | UUID | The battle's UUID from `PokemonBattle.battleId` |
| allyShowdownIds | List\ | Showdown IDs for player side (e.g., `["p1", "p3", "p5", "p7"]`) |
| enemyShowdownIds | List\ | Showdown IDs for boss side (e.g., `["p2"]`) |
**Kotlin Codec:**
```kotlin
// Write
buf.writeUuid(battleId)
buf.writeInt(allyShowdownIds.size)
allyShowdownIds.forEach { buf.writeString(it) }
buf.writeInt(enemyShowdownIds.size)
enemyShowdownIds.forEach { buf.writeString(it) }
// Read
val battleId = buf.readUuid()
val allyCount = buf.readInt()
val allyIds = (0 until allyCount).map { buf.readString() }
val enemyCount = buf.readInt()
val enemyIds = (0 until enemyCount).map { buf.readString() }
```
### Showdown ID Convention
Showdown assigns IDs based on battle position:
- **p1, p3, p5, p7** - Odd numbers for one side (use for players)
- **p2, p4, p6, p8** - Even numbers for opposite side (use for boss)
In a 4v1 raid:
- Player 1 → p1
- Player 2 → p3
- Player 3 → p5
- Player 4 → p7
- Boss → p2
### When to Send RaidSideConfig
**Timing is critical.** Send the packet:
1. **After** `BattleRegistry.startBattle()` returns
2. **Before** the player opens the battle UI (they have ~1-2 seconds)
Recommended: Send immediately, then send again after 3 seconds (60 ticks) as a safety net in case the client wasn't ready.
```kotlin
// Immediately after battle starts
for (player in players) {
ServerPlayNetworking.send(player, RaidSideConfigPayload(battle.battleId, allyIds, enemyIds))
}
// Safety net - resend after 3 seconds
Scheduler.schedule(60) {
for (player in players) {
ServerPlayNetworking.send(player, RaidSideConfigPayload(battle.battleId, allyIds, enemyIds))
}
}
```
### What the Client Does
When Eclipse Core Client receives `RaidSideConfigPayload`:
1. Finds the active `ClientBattle` matching `battleId`
2. Collects all `ClientBattleActor` from both sides
3. Reorganizes them: allies → `side1`, enemies → `side2`
4. Sets `isInRaidBattle = true` (enables targeting override)
This fixes:
- Pokemon appearing on wrong visual side
- Players unable to target the boss (adjacency override kicks in)
### Payload Classes (Fabric 1.21.1)
```kotlin
// Register in your mod initializer
PayloadTypeRegistry.playC2S().register(ClientHandshakePayload.ID, ClientHandshakePayload.CODEC)
PayloadTypeRegistry.playS2C().register(HandshakeAckPayload.ID, HandshakeAckPayload.CODEC)
PayloadTypeRegistry.playS2C().register(RaidSideConfigPayload.ID, RaidSideConfigPayload.CODEC)
// Handle incoming handshakes
ServerPlayNetworking.registerGlobalReceiver(ClientHandshakePayload.ID) { payload, context ->
val player = context.player()
// Track that this player has the client mod
// Send acknowledgment back
}
```
### Without This Client Mod
If a player doesn't have Eclipse Core Client:
- Normal 1v1 and 2v2 battles work fine
- Raid battles will have broken targeting (players can't select the boss)
- Pokemon may appear on wrong sides visually
Block raid participation for players without the client mod by checking your handshake tracking.
## Compatibility
This mod is designed to work with server-side raid mods that implement the Eclipse packet protocol. Without a compatible server mod, it functions as a passive gimmick fix for doubles/triples battles.
## Support
This mod is provided as-is for use with compatible Eclipse server mods.