diff --git a/docs/protocol/index.md b/docs/protocol/index.md index 8067d856..b7c205a3 100644 --- a/docs/protocol/index.md +++ b/docs/protocol/index.md @@ -37,62 +37,62 @@ pages is a per-iteration documentation task — see CONTRIBUTING.md. | ID | Packet | Direction | Server handler | Client handler | Detail | |---|---|---|---|---|---| -| 1 | `P_CreateAccount` | C→S | [ServerNet.bb:2309](../../src/Modules/ServerNet.bb#L2309) | — | — | -| 2 | `P_VerifyAccount` | C→S | [ServerNet.bb:2363](../../src/Modules/ServerNet.bb#L2363) | — | — | -| 3 | `P_FetchCharacter` | C→S | [ServerNet.bb:2555](../../src/Modules/ServerNet.bb#L2555) | — | — | -| 4 | `P_CreateCharacter` | C→S | [ServerNet.bb:2679](../../src/Modules/ServerNet.bb#L2679) | — | — | -| 5 | `P_DeleteCharacter` | C→S | [ServerNet.bb:2883](../../src/Modules/ServerNet.bb#L2883) | — | — | -| 6 | `P_ChangePassword` | C→S | [ServerNet.bb:2498](../../src/Modules/ServerNet.bb#L2498) | — | — | -| 7 | `P_FetchActors` | C→S | [ServerNet.bb:2201](../../src/Modules/ServerNet.bb#L2201) | — | — | +| 1 | `P_CreateAccount` | C→S | [ServerNet.bb:2310](../../src/Modules/ServerNet.bb#L2310) | — | — | +| 2 | `P_VerifyAccount` | C→S | [ServerNet.bb:2364](../../src/Modules/ServerNet.bb#L2364) | — | — | +| 3 | `P_FetchCharacter` | C→S | [ServerNet.bb:2556](../../src/Modules/ServerNet.bb#L2556) | — | — | +| 4 | `P_CreateCharacter` | C→S | [ServerNet.bb:2680](../../src/Modules/ServerNet.bb#L2680) | — | — | +| 5 | `P_DeleteCharacter` | C→S | [ServerNet.bb:2884](../../src/Modules/ServerNet.bb#L2884) | — | — | +| 6 | `P_ChangePassword` | C→S | [ServerNet.bb:2499](../../src/Modules/ServerNet.bb#L2499) | — | — | +| 7 | `P_FetchActors` | C→S | [ServerNet.bb:2202](../../src/Modules/ServerNet.bb#L2202) | — | — | | 8 | `P_FetchItems` | Unused | — | — | — | -| 9 | `P_ChangeArea` | Both | [ServerNet.bb:721](../../src/Modules/ServerNet.bb#L721) | [ClientNet.bb:1616](../../src/Modules/ClientNet.bb#L1616) | — | -| 10 | `P_FetchUpdateFiles` | C→S | [ServerNet.bb:2186](../../src/Modules/ServerNet.bb#L2186) | — | — | +| 9 | `P_ChangeArea` | Both | [ServerNet.bb:722](../../src/Modules/ServerNet.bb#L722) | [ClientNet.bb:1616](../../src/Modules/ClientNet.bb#L1616) | — | +| 10 | `P_FetchUpdateFiles` | C→S | [ServerNet.bb:2187](../../src/Modules/ServerNet.bb#L2187) | — | — | | 11 | `P_NewActor` | S→C | — | [ClientNet.bb:1575](../../src/Modules/ClientNet.bb#L1575) | — | -| 12 | `P_StartGame` | C→S | [ServerNet.bb:2065](../../src/Modules/ServerNet.bb#L2065) | — | — | +| 12 | `P_StartGame` | C→S | [ServerNet.bb:2066](../../src/Modules/ServerNet.bb#L2066) | — | — | | 13 | `P_ActorGone` | S→C | — | [ClientNet.bb:1550](../../src/Modules/ClientNet.bb#L1550) | — | -| 14 | `P_StandardUpdate` | Both | [ServerNet.bb:1761](../../src/Modules/ServerNet.bb#L1761) | [ClientNet.bb:1490](../../src/Modules/ClientNet.bb#L1490) | [P_StandardUpdate](packets/P_StandardUpdate.md) | -| 15 | `P_InventoryUpdate` | Both | [ServerNet.bb:1573](../../src/Modules/ServerNet.bb#L1573) | [ClientNet.bb:1271](../../src/Modules/ClientNet.bb#L1271) | [P_InventoryUpdate](packets/P_InventoryUpdate.md) | +| 14 | `P_StandardUpdate` | Both | [ServerNet.bb:1762](../../src/Modules/ServerNet.bb#L1762) | [ClientNet.bb:1490](../../src/Modules/ClientNet.bb#L1490) | [P_StandardUpdate](packets/P_StandardUpdate.md) | +| 15 | `P_InventoryUpdate` | Both | [ServerNet.bb:1574](../../src/Modules/ServerNet.bb#L1574) | [ClientNet.bb:1271](../../src/Modules/ClientNet.bb#L1271) | [P_InventoryUpdate](packets/P_InventoryUpdate.md) | | 16 | `P_ChatMessage` | Both | [ServerNet.bb:183](../../src/Modules/ServerNet.bb#L183) | [ClientNet.bb:1213](../../src/Modules/ClientNet.bb#L1213) | — | | 17 | `P_WeatherChange` | S→C | — | [ClientNet.bb:1266](../../src/Modules/ClientNet.bb#L1266) | — | -| 18 | `P_AttackActor` | Both | [ServerNet.bb:1547](../../src/Modules/ServerNet.bb#L1547) | [ClientNet.bb:1109](../../src/Modules/ClientNet.bb#L1109) | — | +| 18 | `P_AttackActor` | Both | [ServerNet.bb:1548](../../src/Modules/ServerNet.bb#L1548) | [ClientNet.bb:1109](../../src/Modules/ClientNet.bb#L1109) | [P_AttackActor](packets/P_AttackActor.md) | | 19 | `P_ActorDead` | S→C | — | [ClientNet.bb:1065](../../src/Modules/ClientNet.bb#L1065) | — | -| 20 | `P_RightClick` | C→S | [ServerNet.bb:1419](../../src/Modules/ServerNet.bb#L1419) | — | — | -| 21 | `P_Dialog` | Both | [ServerNet.bb:1265](../../src/Modules/ServerNet.bb#L1265) | [ClientNet.bb:1021](../../src/Modules/ClientNet.bb#L1021) | — | +| 20 | `P_RightClick` | C→S | [ServerNet.bb:1420](../../src/Modules/ServerNet.bb#L1420) | — | — | +| 21 | `P_Dialog` | Both | [ServerNet.bb:1266](../../src/Modules/ServerNet.bb#L1266) | [ClientNet.bb:1021](../../src/Modules/ClientNet.bb#L1021) | — | | 22 | `P_StatUpdate` | S→C | — | [ClientNet.bb:990](../../src/Modules/ClientNet.bb#L990) | — | | 23 | `P_QuestLog` | S→C | — | [ClientNet.bb:949](../../src/Modules/ClientNet.bb#L949) | — | | 24 | `P_GoldChange` | S→C | — | [ClientNet.bb:941](../../src/Modules/ClientNet.bb#L941) | — | | 25 | `P_NameChange` | S→C | — | [ClientNet.bb:930](../../src/Modules/ClientNet.bb#L930) | — | | 26 | `P_KnownSpellUpdate` | S→C | — | [ClientNet.bb:817](../../src/Modules/ClientNet.bb#L817) | — | -| 27 | `P_SpellUpdate` | C→S | [ServerNet.bb:1117](../../src/Modules/ServerNet.bb#L1117) | — | — | +| 27 | `P_SpellUpdate` | C→S | [ServerNet.bb:1118](../../src/Modules/ServerNet.bb#L1118) | — | — | | 28 | `P_CreateEmitter` | S→C | — | [ClientNet.bb:766](../../src/Modules/ClientNet.bb#L766) | — | | 29 | `P_Sound` | S→C | — | [ClientNet.bb:733](../../src/Modules/ClientNet.bb#L733) | — | | 30 | `P_AnimateActor` | S→C | — | [ClientNet.bb:707](../../src/Modules/ClientNet.bb#L707) | — | -| 31 | `P_ActionBarUpdate` | C→S | [ServerNet.bb:1085](../../src/Modules/ServerNet.bb#L1085) | — | — | +| 31 | `P_ActionBarUpdate` | C→S | [ServerNet.bb:1086](../../src/Modules/ServerNet.bb#L1086) | — | — | | 32 | `P_XPUpdate` | S→C | — | [ClientNet.bb:683](../../src/Modules/ClientNet.bb#L683) | — | | 33 | `P_ScreenFlash` | S→C | — | [ClientNet.bb:673](../../src/Modules/ClientNet.bb#L673) | — | | 34 | `P_Music` | S→C | — | [ClientNet.bb:752](../../src/Modules/ClientNet.bb#L752) | — | -| 35 | `P_OpenTrading` | Both | [ServerNet.bb:810](../../src/Modules/ServerNet.bb#L810) | [ClientNet.bb:576](../../src/Modules/ClientNet.bb#L576) | — | +| 35 | `P_OpenTrading` | Both | [ServerNet.bb:811](../../src/Modules/ServerNet.bb#L811) | [ClientNet.bb:576](../../src/Modules/ClientNet.bb#L576) | — | | 36 | `P_ActorEffect` | S→C | — | [ClientNet.bb:487](../../src/Modules/ClientNet.bb#L487) | — | | 37 | `P_Projectile` | S→C | — | [ClientNet.bb:217](../../src/Modules/ClientNet.bb#L217) | — | | 38 | `P_PartyUpdate` | S→C | — | [ClientNet.bb:477](../../src/Modules/ClientNet.bb#L477) | — | | 39 | `P_AppearanceUpdate` | S→C | — | [ClientNet.bb:268](../../src/Modules/ClientNet.bb#L268) | — | | 40 | `P_CloseTrading` | S→C | — | [ClientNet.bb:567](../../src/Modules/ClientNet.bb#L567) | — | -| 41 | `P_UpdateTrading` | Both | [ServerNet.bb:773](../../src/Modules/ServerNet.bb#L773) | [ClientNet.bb:527](../../src/Modules/ClientNet.bb#L527) | — | +| 41 | `P_UpdateTrading` | Both | [ServerNet.bb:774](../../src/Modules/ServerNet.bb#L774) | [ClientNet.bb:527](../../src/Modules/ClientNet.bb#L527) | — | | 42 | `P_SelectScenery` | S→C | — | [ClientNet.bb:255](../../src/Modules/ClientNet.bb#L255) | — | -| 43 | `P_ItemScript` | C→S | [ServerNet.bb:1358](../../src/Modules/ServerNet.bb#L1358) | — | — | -| 44 | `P_EatItem` | C→S | [ServerNet.bb:1291](../../src/Modules/ServerNet.bb#L1291) | — | — | +| 43 | `P_ItemScript` | C→S | [ServerNet.bb:1359](../../src/Modules/ServerNet.bb#L1359) | — | — | +| 44 | `P_EatItem` | C→S | [ServerNet.bb:1292](../../src/Modules/ServerNet.bb#L1292) | — | — | | 45 | `P_ItemHealth` | S→C | — | [ClientNet.bb:249](../../src/Modules/ClientNet.bb#L249) | — | -| 46 | `P_Jump` | Both | [ServerNet.bb:1067](../../src/Modules/ServerNet.bb#L1067) | [ClientNet.bb:241](../../src/Modules/ClientNet.bb#L241) | — | -| 47 | `P_Dismount` | C→S | [ServerNet.bb:1746](../../src/Modules/ServerNet.bb#L1746) | — | — | +| 46 | `P_Jump` | Both | [ServerNet.bb:1068](../../src/Modules/ServerNet.bb#L1068) | [ClientNet.bb:241](../../src/Modules/ClientNet.bb#L241) | — | +| 47 | `P_Dismount` | C→S | [ServerNet.bb:1747](../../src/Modules/ServerNet.bb#L1747) | — | — | | 48 | `P_FloatingNumber` | S→C | — | [ClientNet.bb:205](../../src/Modules/ClientNet.bb#L205) | — | -| 49 | `P_RepositionActor` | Both | [ServerNet.bb:714](../../src/Modules/ServerNet.bb#L714) | [ClientNet.bb:180](../../src/Modules/ClientNet.bb#L180) | — | +| 49 | `P_RepositionActor` | Both | [ServerNet.bb:715](../../src/Modules/ServerNet.bb#L715) | [ClientNet.bb:180](../../src/Modules/ClientNet.bb#L180) | — | | 50 | `P_Speech` | S→C | — | [ClientNet.bb:727](../../src/Modules/ClientNet.bb#L727) | — | -| 51 | `P_ProgressBar` | Both | [ServerNet.bb:1254](../../src/Modules/ServerNet.bb#L1254) | [ClientNet.bb:151](../../src/Modules/ClientNet.bb#L151) | — | +| 51 | `P_ProgressBar` | Both | [ServerNet.bb:1255](../../src/Modules/ServerNet.bb#L1255) | [ClientNet.bb:151](../../src/Modules/ClientNet.bb#L151) | — | | 52 | `P_BubbleMessage` | S→C | — | [ClientNet.bb:1203](../../src/Modules/ClientNet.bb#L1203) | — | -| 53 | `P_ScriptInput` | Both | [ServerNet.bb:1286](../../src/Modules/ServerNet.bb#L1286) | [ClientNet.bb:1014](../../src/Modules/ClientNet.bb#L1014) | — | +| 53 | `P_ScriptInput` | Both | [ServerNet.bb:1287](../../src/Modules/ServerNet.bb#L1287) | [ClientNet.bb:1014](../../src/Modules/ClientNet.bb#L1014) | — | | 60 | `P_KickedPlayer` | S→C | — | [ClientNet.bb:1763](../../src/Modules/ClientNet.bb#L1763) | — | -| 61 | `P_Examine` | C→S | [ServerNet.bb:1482](../../src/Modules/ServerNet.bb#L1482) | — | — | -| 62 | `P_Trade` | C→S | [ServerNet.bb:1523](../../src/Modules/ServerNet.bb#L1523) | — | — | +| 61 | `P_Examine` | C→S | [ServerNet.bb:1483](../../src/Modules/ServerNet.bb#L1483) | — | — | +| 62 | `P_Trade` | C→S | [ServerNet.bb:1524](../../src/Modules/ServerNet.bb#L1524) | — | — | --- diff --git a/docs/protocol/packets/P_AttackActor.md b/docs/protocol/packets/P_AttackActor.md new file mode 100644 index 00000000..bdac4efe --- /dev/null +++ b/docs/protocol/packets/P_AttackActor.md @@ -0,0 +1,110 @@ +# P_AttackActor + +**Direction:** Both (C→S "I'm attacking this target"; S→C "the attack resolved") +**Numeric ID:** 18 +**Server handler:** [ServerNet.bb:1548](../../../src/Modules/ServerNet.bb#L1548) +**Server attack engine:** [GameServer.bb:300](../../../src/Modules/GameServer.bb#L300) (`ActorAttack`) +**Client handler:** [ClientNet.bb:1109](../../../src/Modules/ClientNet.bb#L1109) + +## Purpose + +The combat packet. Carries melee + ranged-projectile attack initiation from client to server, and the resolved attack (hit / miss / observer-visible swing) from server to client. The server runs the entire damage formula, faction check, weapon-range check, and HP mutation; the client only animates and updates its HUD. + +## Field layout + +### C → S — "I'm attacking RuntimeID X" + +A fixed 2-byte payload, no sub-code. The client tags the target by `RuntimeID`; the server resolves to `ActorInstance` via `RuntimeIDList`. + +| Offset | Width | Type | Field | Notes | +|---|---|---|---|---| +| 1 | 2 | Int | Target `RuntimeID` | The actor the client wants to hit. Resolved server-side via `RuntimeIDList`. | + +**Total: 2 bytes.** Validated by `Len(M\MessageData$) = 2` ([ServerNet.bb:1550](../../../src/Modules/ServerNet.bb#L1550)) — packets of any other length are silently dropped. + +### S → C — three sub-codes covering attacker, victim, and observers + +| Sub-code | Recipient | Layout | Purpose | +|---|---|---|---| +| `"H"` | Attacker (`A1\RNID`) | `1B sub + 2B Victim\RuntimeID + 2B Damage+1 + 1B DamageType` | "I hit them" — for the attacker's HUD (damage numbers, attack animation). | +| `"Y"` | Victim (`A2\RNID`) | `1B sub + 2B Attacker\RuntimeID + 2B Damage+1 + 1B DamageType` | "They hit me" — for the victim's HUD (incoming damage, parry/hit animation). | +| `"O"` | All other players in the same area | `1B sub + 2B Attacker\RuntimeID + 2B Victim\RuntimeID` (no damage payload) | Observer swing animation. Observers re-sync HP via the next `P_StatUpdate`, so the damage isn't replicated on this channel. Subtle: the ClientNet "Else" branch doesn't decode a fresh `Damage` from the wire — in non-Strict `UpdateNetwork()`, `Damage` is an implicit function-scope variable that persists across `Select Case` iterations within one call, so it reads whatever the prior `H`/`Y` packet (or zero, on the first call) left there. Current behaviour is benign because `P_StatUpdate` re-syncs HP authoritatively. | + +**Damage+1 encoding:** The 2-byte `Damage` field carries `Damage + 1` so a value of 0 on the wire means "miss" (rendered as a parry animation). The wire field can be negative (signed 2-byte = -32768..32767), which lets the server signal a miss explicitly. Client subtracts 1 to recover the actual damage at [ClientNet.bb:1117](../../../src/Modules/ClientNet.bb#L1117) / [:1145](../../../src/Modules/ClientNet.bb#L1145). + +**Damage type bounds:** The 1-byte `DamageType` is clamped client-side to `[0, 19]` before indexing into `DamageTypes$` ([ClientNet.bb:1121-1123](../../../src/Modules/ClientNet.bb#L1121)). A malformed packet with a wild value falls back to type 0 instead of crashing the client. + +## Validation requirements + +### C → S handler ([ServerNet.bb:1548-1571](../../../src/Modules/ServerNet.bb#L1548)) + +Six gates, all required to fire: + +1. **Sender validity**: `AI <> Null` (FindActorInstanceFromRNID resolves the sender). +2. **Packet shape**: `Len(M\MessageData$) = 2`. +3. **Combat delay**: `MilliSecs() - AI\LastAttack >= CombatDelay`. Prevents attack-spam cheating; `AI\LastAttack` is set on every successful attack in `ActorAttack`. +4. **Not riding a mount**: `AI\Mount = Null`. Mounted players can't attack (intentional gameplay constraint). +5. **Same-area gate** (added PR [#276](https://github.com/RydeTec/rcce2/pull/276)): both attacker and victim must be in the same `AreaInstance`. Resolved via `Object.AreaInstance(AI\ServerArea)` and `Object.AreaInstance(A2\ServerArea)`; the dual lookup guards both sides against stale `ServerArea` mid-portal. +6. **PvP / NPC permission**: `A2\RNID < 0 Or AInstance\Area\PvP = True`. NPCs (RNID -1) are always attackable; players are only attackable in PvP-enabled areas. + +### `ActorAttack` damage engine ([GameServer.bb:300-600+](../../../src/Modules/GameServer.bb#L300)) + +The damage engine itself runs additional checks: + +- **Already-dead target**: `If A2\Attributes\Value[HealthStat] <= 0 Then Return False`. Without this, two attackers landing in the same tick both saw HP > 0, both subtracted, both called `KillActor` against freed memory (double-XP + use-after-free). +- **Both Aggressiveness ≠ 3**: NPCs with `Aggressiveness = 3` are non-combatant (typed mobs / vendors). +- **Faction rating**: `A1\FactionRatings[A2\HomeFaction] > 150` blocks the attack (friendly faction). +- **Range check**: melee uses `7.0 + A1\Actor\Radius + A2\Actor\Radius` squared. Ranged projectile uses `weapon.Range + A1.Radius + A2.Radius` squared. + +### Hit chance + damage formula (4 variants, selected by `CombatFormula` global) + +| `CombatFormula` | Hit chance | Damage formula | +|---|---|---| +| `1` (Normal) | 90% | `weapon.Damage ± strength-rolled bonus`, critical 1/10 (×2). Armour subtracts `GetArmourLevel + Resistances[DamageType] - 100 + ToughnessStat / 8`. | +| `2` (No strength bonus) | 90% | `weapon.Damage` flat. Critical 1/10 (×2). Same armour formula. | +| `3` (Multiplied) | 90% | `weapon.Damage × Strength`. Critical 1/10. | +| `4` (Attack script) | N/A | Delegates to a `ThreadScript("Attack", "Main", attacker, victim)` — content authors implement the formula in `.rsl`. No range/damage check server-side. | + +`CombatFormula` is a global set at server boot from project config. The attack-script variant (4) is the modder hook for completely custom combat. + +### Broadcast pattern + +After damage is applied, the server emits three packets: + +1. To attacker (if `A1\RNID > 0`): `"H" + victim_rid + damage + dtype` +2. To victim (if `A2\RNID > 0`): `"Y" + attacker_rid + damage + dtype` +3. To all others in the same `AInstance\FirstInZone` chain: `"O" + attacker_rid + victim_rid` (no damage) + +The "O" loop ([GameServer.bb:575-584](../../../src/Modules/GameServer.bb#L575)) skips A1 and A2 (they already got their personalised packet) and skips Null-AreaInstance (stale-area mid-portal). + +## Anti-cheat surface + +`P_AttackActor` is one of the most security-sensitive packets — a single attack hit/miss decision drives PvP outcomes. The validation requirements above cover the known attack surface; the recent same-area gate (#276) was the most-recently-added defence (specifically against cross-area packet injection that would have bypassed PvP rules). + +The handler is **NOT** privilege-gated like the BVM clicker handlers — combat is the player's privilege; the gate is "are you allowed to fight this target?" not "are you allowed to call this function?". + +## Historical bugs / PR references + +| PR | Fixed | +|---|---| +| [#276](https://github.com/RydeTec/rcce2/pull/276) | Same-area gate (cross-area injection prevention) | +| Two-attackers-same-tick fix (pre-PR) | The already-dead target guard at GameServer.bb:308 — prevents double KillActor + use-after-free | +| Defensive AInstance Null check | The broadcast loop at GameServer.bb:575 skips when AInstance is Null (mid-warp race) | +| [#282](https://github.com/RydeTec/rcce2/pull/282) | `FindActorInstanceFromRNID(M\FromID)` -- O(1) sender resolution | +| [#283](https://github.com/RydeTec/rcce2/pull/283) | Per-area `FirstInZone` chain walk in the observer broadcast loop | +| [#287](https://github.com/RydeTec/rcce2/pull/287) | Pet-aggro broadcast (`ActorAttack`'s pet recruitment) now walks the per-leader `FirstSlave` chain | + +## Related packets + +- [`P_StandardUpdate`](P_StandardUpdate.md) — movement, including the speed-hack clamp that prevents teleport-into-range attacks +- [`P_StatUpdate`](../index.md) — broadcasts HP changes; observers see victim's HP drop via this rather than the "O" P_AttackActor +- [`P_ActorDead`](../index.md) — broadcast when victim's HP drops ≤ 0 +- [`P_Projectile`](../index.md) — projectile-launch broadcast (separate from this packet; ranged attacks emit both) +- [`P_ActorEffect`](../index.md) — debuff / status effects that combat triggers + +## See also + +- [`../encoding.md`](../encoding.md) — `RCE_StrFromInt$` byte widths +- [`../handler-conventions.md`](../handler-conventions.md) — soft-fail discipline, bounds-check, same-area gate pattern +- [`../../modules/servernet.md`](../../modules/servernet.md) — P_AttackActor's place in the dispatch +- [`GameServer.bb`'s `ActorAttack` function](../../../src/Modules/GameServer.bb#L300) — the full damage engine