Real-time survival-to-creative phantom mirror plugin
- Java 100%
- NmsHelper: ALL Method/Constructor/Field cached at init(), zero getMethod() calls during gameplay (~5-10x faster per NMS call) - Distance culling: skip teleport/equip packets for viewers >128 blocks - Respawn interval: 5s → 15s (teleport handles movement between) - Particles disabled by default (cosmetic overhead) - send() method cached after first resolution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| gradle/wrapper | ||
| src | ||
| velocity-plugin | ||
| .gitignore | ||
| build.gradle.kts | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| README.md | ||
| settings.gradle.kts | ||
EkaiiMirror
Real-time survival → creative server mirroring plugin for Minecraft 26.1.2 (Luminol/Folia).
Players on the creative server see survival players as phantom entities with full skin, armor, items, sneaking, elytra flying, and vehicle riding — all in real time.
Features
Phantom Players (NMS)
- Full player model with skin (via NMS ServerPlayer + reflection)
- Real-time position + head rotation sync (100ms updates)
- Sneaking (Pose.CROUCHING + shared flags)
- Arm swing animation
- Full equipment sync: main hand, off hand, helmet, chestplate, leggings, boots
- Elytra flying (Pose.FALL_FLYING + flag 0x80)
- Vehicle riding: boats, horses, minecarts, camels, etc.
- Glow outline (scoreboard team color, default AQUA)
- Soul flame particles
- Periodic respawn (5s) handles: late joins, dimension changes, reconnections
Ghost Blocks
- Block place/break events mirrored as client-side fake blocks
- Uses
player.sendBlockChange()— no server-side modification - Batched for performance (configurable flush interval)
Cross-Server (Velocity Plugin)
- Chat bridge: messages forwarded between all servers
/world survie|crea|plotcommand to switch servers- Tab list: cross-server player entries via Velocity
Architecture
Sender (Survie) → Redis pub/sub → Receiver (CreaClone)
channel: ekaii:mirror
Velocity Plugin: chat bridge + /world command + tab sync
Message Protocol
Compact JSON over Redis pub/sub. Message types:
bpBlock Place,bbBlock BreakpjPlayer Join,pqPlayer QuitpmPlayer Move,psPlayer Sneak,paPlayer SwingpePlayer Equipment (6 slots)elPlayer Elytra,vmVehicle Mount,vdVehicle DismounthbHeartbeat,baBatch
NMS Approach
PacketEvents was found to silently drop all entity spawn packets on Luminol/Folia. The plugin uses pure Java reflection to access NMS classes at runtime — zero compile-time NMS dependencies.
Key NMS packets used:
ClientboundPlayerInfoUpdatePacket(player profile + skin)ClientboundAddEntityPacket(spawn entity, raw values constructor)ClientboundSetEntityDataPacket(metadata: glow, sneak, elytra flags)ClientboundTeleportEntityPacket(position sync via PositionMoveRotation)ClientboundRotateHeadPacket(head rotation)ClientboundSetEquipmentPacket(all 6 equipment slots)ClientboundSetPassengersPacket(vehicle riding, via Unsafe field injection)ClientboundRemoveEntitiesPacket(despawn)ClientboundAnimatePacket(arm swing)
Installation
Requirements
- Minecraft 26.1.2 server (Luminol, Folia, or Paper)
- Redis server
- Velocity proxy (for cross-server features)
Backend Plugin
- Drop
EkaiiMirror-1.0.0.jarinplugins/on both servers - Configure
plugins/EkaiiMirror/config.yml:
Sender (survival server):
mode: SENDER
redis:
host: "<redis-host>"
port: 6379
password: ""
channel: "ekaii:mirror"
sender:
worlds: [] # empty = all worlds (overworld, nether, end)
move-interval-ms: 100
batch-interval-ms: 50
receiver:
glow-color: AQUA
block-display: true
particles: true
Receiver (creative server):
mode: RECEIVER
redis:
host: "<redis-host>"
port: 6379
password: ""
channel: "ekaii:mirror"
sender:
worlds: []
move-interval-ms: 100
batch-interval-ms: 50
receiver:
glow-color: AQUA
block-display: true
particles: true
Velocity Plugin
- Drop
EkaiiMirrorVelocity-1.0.0.jarin Velocity'splugins/ - Configure
plugins/ekaii-mirror-velocity/config.yml:
servers:
ekaii:
display: "Survie"
color: "gray"
creaclone:
display: "Créa"
color: "green"
plot:
display: "Plot"
color: "gold"
shared-chat: true
shared-tab: true
Redis
docker run -d --name ekaii-redis --restart unless-stopped --network host redis:7-alpine
Commands
| Command | Description |
|---|---|
/mirror toggle |
Toggle phantom visibility (receiver only) |
/mirror status |
Show plugin status |
/world survie |
Switch to survival server |
/world crea |
Switch to creative server |
/world plot |
Switch to plot server |
Building
./gradlew :shadowJar # Backend plugin
./gradlew :velocity-plugin:shadowJar # Velocity plugin
Output:
build/libs/EkaiiMirror-1.0.0.jarvelocity-plugin/build/libs/EkaiiMirrorVelocity-1.0.0.jar
Known Limitations
- PacketEvents
sendPacketdoes not work on Luminol/Folia for entity spawning — NMS reflection is used instead - Vehicle position syncs via teleport (may appear slightly jerky at high latency)
- Equipment updates every ~2 seconds (not instant on item pickup)
- Phantom players are purely visual — no collision, no interaction