Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 28 additions & 28 deletions docs/protocol/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) | — | — |

---

Expand Down
110 changes: 110 additions & 0 deletions docs/protocol/packets/P_AttackActor.md
Original file line number Diff line number Diff line change
@@ -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).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe Damage+1 wire field as unsigned

This sentence documents the 2-byte damage payload as signed and says negatives are used to signal misses, but the actual codec path writes Damage + 1 with RCE_StrFromInt(..., 2) and reads it with RCE_IntFromStr after zeroing upper bytes, so the on-wire value is effectively 0..65535 and miss is encoded as 0 (client then subtracts 1). Keeping the signed-short claim here can mislead alternate client/server implementations into decoding values >= 32768 incorrectly and diverging from server combat outcomes.

Useful? React with 👍 / 👎.


**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. |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Correct CombatFormula 4 range-check behavior

The table states that formula 4 has no server-side range check, but that is only true for the ranged-weapon scripted branch; melee scripted attacks still pass through the shared distance gate before the CombatFormula = 4 dispatch. This overgeneralization weakens the protocol doc’s accuracy for anti-cheat analysis by implying melee script combat bypasses a guard that is actually enforced.

Useful? React with 👍 / 👎.


`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
Loading