# 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