# Hijack Architecture This page documents the internals of `/recap hijack` for plugin authors and contributors. The user-facing behavior is in [[Recap/Server Owners/Features/Hijack]]. --- ## The challenge Plain Recap playback uses packet-only NPCs — fake entities sent to clients via `WrapperPlayServerSpawnEntity` etc. They render but don't exist server-side, so vanilla combat, damage, knockback, and death drops don't apply. Hijack needs vanilla damage flow. That means each NPC must be a **real entity** in the world. But it can't be a real player either — no human is logged in to drive it. --- ## Solution: NMS ServerPlayer + FakeConnection For each scene entry, hijack spawns an actual `net.minecraft.server.level.ServerPlayer` and attaches a fake connection so vanilla doesn't try to send packets back to a real socket. ``` nmsWorld.addNewPlayer(serverPlayer) ▲ │ ServerPlayer │ ├── GameProfile (recorded skin texture+signature) │ ├── ClientInformation (dummy en_us, 8-chunk view, etc.) │ └── connection: FakeServerGamePacketListener │ └── FakeConnection : net.minecraft.network.Connection │ └── FakeChannel : io.netty.channel.Channel │ └── all sends are no-ops └── valid = true; supressTrackerForLogin = false ``` `FakeConnection` extends vanilla `Connection`, no-ops all sends, signals listener `onSuccess()` immediately to mimic a healthy socket. `FakeChannel` is a stub `io.netty.channel.Channel` with an inline `EventLoop` that runs all tasks synchronously. `FakeServerGamePacketListener` extends `ServerGamePacketListenerImpl` and overrides every `handle*` method to no-op; it clears teleport-acknowledgement fields via reflection in `tick()` so vanilla's "did the client accept our teleport?" gate stays open. This pattern is ported from Lodestone's Catalyst plugin's Kotlin FakeConnection. --- ## Spawn flow 1. Construct `GameProfile` with recorded skin properties. 2. `new ServerPlayer(nmsServer, nmsWorld, gameProfile, clientInfo)`. 3. `setPos`, `setYRot`, `setXRot`, `setHealth(20)` from first recorded frame. 4. Attach `FakeConnection` + `FakeServerGamePacketListener` as `serverPlayer.connection`. 5. `nmsWorld.addNewPlayer(serverPlayer)` — joins entity tracker. 6. Disable entity-entity collision so hijackers can't push the NPC. 7. Apply first-frame inventory via direct NMS `NonNullList.set` (skipping per-slot broadcast — see the bundle-overflow note in `applyFullInventory`). 8. Manually broadcast `PlayerInfoUpdate(ADD_PLAYER)` + `SpawnEntity` because vanilla's chunk tracker doesn't auto-broadcast for new `Player` entities. 9. Schedule `PlayerInfoRemove` 2 ticks later so the NPC doesn't pollute the tab list. --- ## Per-tick flow Pre-touch (NPC on recorded rails): ```java serverPlayer.absMoveTo(rx, ry, rz, frame.yaw, frame.pitch); ``` Post-touch (hijacker has hit the NPC): ```java serverPlayer.travel(Vec3.ZERO); // applies gravity, drag, friction, knockback velocity ``` The `serverPlayerTouched` flag flips in `HijackPacketListener` / `HijackDamageListener` when a real attack lands. After that, vanilla physics owns the entity's motion. --- ## Damage detection `HijackDamageListener` listens for Bukkit `EntityDamageByEntityEvent` and computes the post-damage HP. If the post-damage HP would be ≤ 0, the actor's `pendingHijackDeath` flag is set; the actor's next tick fires `killNpc()` which sends the death animation packet and queues a destroy. Why event-driven, not polled? Polling `serverPlayer.getHealth()` is racy — `doImmediateRespawn` gamerule and respawn plugins reset HP before the next tick can observe it. --- ## Reactive recording Each actor maintains a side `reactiveWriter` that captures the NPC's modified-by-physics behavior frame-by-frame: ```java ActionFrame f = new ActionFrame(); f.x = prevX - offsetX; // de-offset back to recording space f.y = prevY - offsetY; f.z = prevZ - offsetZ; f.yaw = yaw; f.pitch = pitch; f.velX = vx; // post-physics velocity // ... full state passthrough ... f.died = source.died || hijackDiedThisFrame; reactiveWriter.addFrame(f); ``` On `/recap stop`, touched actors swap their scene entry from the original recording to this reactive recording. --- ## See Also - [[Recap/Server Owners/Features/Hijack]] — user-facing workflow - [[Recap/Developers/Recording Format]] — what's serialized into the reactive stream - [[Recap/Developers/Packet Capture]] — replay rate-limiting