# R2 — Retail AC Combat System (Deep Dive)

**Scope:** The complete physical combat system as shipped in retail Asheron's
Call: attack modes, attack types, attack heights, the power/accuracy bar,
the full damage formula, crit mechanics, evasion, body-part targeting,
resistances, wire format, and PvP differences. This is the authoritative
reference for porting combat into **acdream**.

**Sources used:**

- `acclient.exe` decompilation, primarily `chunk_00560000.c` (combat HUD +
  notification handlers) — FUN_0056AC80 family and FUN_0056D370 family
  have the "You hit / You evaded / Critical!" text formatting and the
  AttackConditions bit-decode. Client-side combat is mostly *presentation*;
  the math lives on the server, but the wire format and the constants the
  client bakes in are ground truth.
- **ACE** server-side port: `DamageEvent.cs`, `Player_Combat.cs`,
  `Player_Melee.cs`, `Creature_Combat.cs`, `Creature_BodyPart.cs`,
  `BodyPartTable.cs`, `WorldObject_Weapon.cs`, `SkillCheck.cs`,
  `SkillFormula.cs`, `Armor.cs`, `AttackQueue.cs`. ACE's formulas are
  the most complete open-source reconstruction; they match retail pcaps
  and data-mined formulas.
- **Chorizite.ACProtocol** generated types for the wire definitions of
  C2S `Combat_TargetedMeleeAttack`/`TargetedMissileAttack` and S2C
  `Combat_HandleAttackerNotificationEvent`/`HandleDefenderNotificationEvent`
  plus evasion + death.
- `docs/research/acclient_function_map.md` for correlating decompiled FUNs.

> **A note on retail vs ACE.** The retail client does **not** calculate
> damage — it sends intent (`target_guid`, `attack_height`, `power_level`)
> and receives the resulting damage number + flags. The formulas below
> are the server's job; for acdream (a client), we implement them so we
> can (a) predict outcomes for the power bar preview, (b) verify what we
> receive against what we'd expect, and (c) so the plugin API can surface
> "why did my attack do 47?" telemetry. The actual authority is whatever
> the ACE server sends back.

---

## 1. Attack modes (CombatMode enum)

Retail has five values, bit-packed. Source:
`ACE.Entity.Enum.CombatMode`:

| Bit    | Name        | Purpose                                             |
|--------|-------------|-----------------------------------------------------|
| 0x00   | `Undef`     | Uninitialized / server-side only                    |
| 0x01   | `NonCombat` | "Peace mode" — weapon sheathed, cannot attack       |
| 0x02   | `Melee`     | Weapon drawn, melee swing animations available      |
| 0x04   | `Missile`   | Bow/crossbow/atlatl drawn, arrow nocked             |
| 0x08   | `Magic`     | Wand/orb ready, casting stance                      |

Helpers: `ValidCombat = NonCombat|Melee|Missile|Magic`,
`CombatCombat = Melee|Missile|Magic` (anything that actually swings).

**Transitions.** The client sends `Combat_ChangeCombatMode` (GameAction
0x0053) carrying a single `uint32 Mode`. The server plays the stance-swap
animations and responds with `PrivateUpdatePropertyInt(CombatMode, newMode)`.
The swap takes real time — you cannot attack during it. ACE's
`SetCombatMode` calls `MotionTable.GetAnimationLength(MotionTableId,
Stance, MotionCommand.Ready, targetMotion)` and queues the attack
window to fire after that animLength. For **weapon swap** (e.g. bow →
sword), the client does `currentStance → NonCombat → HandCombat → NonCombat
→ newStance`, with each hop incurring its own animLength; ACE's
`SwitchCombatStyles()` computes the sum.

**MotionStance** is the fine-grained counterpart tied to held-item
geometry. Values: `NonCombat`, `HandCombat` (unarmed), `SwordCombat`,
`SwordShieldCombat`, `TwoHandedSwordCombat`, `BowCombat`, `CrossbowCombat`,
`AtlatlCombat`, `SlingCombat`, `ThrownWeaponCombat`, `ThrownShieldCombat`,
`DualWieldCombat`, `Magic`. The translation from weapon to stance lives
in `Creature_Combat.GetWeaponStance(WorldObject weapon)`:

```
CombatStyle.OneHanded         → SwordCombat
CombatStyle.OneHandedAndShield→ SwordShieldCombat
CombatStyle.TwoHanded         → TwoHandedSwordCombat
CombatStyle.Bow               → BowCombat
CombatStyle.Crossbow          → CrossbowCombat
CombatStyle.Atlatl            → AtlatlCombat
CombatStyle.Sling             → SlingCombat
CombatStyle.ThrownWeapon      → ThrownWeaponCombat
CombatStyle.ThrownShield      → ThrownShieldCombat
CombatStyle.DualWield         → DualWieldCombat
CombatStyle.Unarmed           → HandCombat
CombatStyle.Magic             → Magic
```

If a shield is also equipped and the stance is SwordCombat or
ThrownWeaponCombat, it is promoted to the `…ShieldCombat` variant.

---

## 2. Attack types (AttackType enum + weapon styles)

Retail stores attack type as a bitflag because a single weapon can offer
multiple animations (thrust OR slash, pickable with the power bar). From
`AttackType.cs`:

| Bit     | Flag               | Notes                                  |
|---------|--------------------|----------------------------------------|
| 0x0001  | `Punch`            | Unarmed jab                            |
| 0x0002  | `Thrust`           | Single-weapon thrust                   |
| 0x0004  | `Slash`            | Single-weapon slash                    |
| 0x0008  | `Kick`             | High-power unarmed                     |
| 0x0010  | `OffhandPunch`     | Dual-wield left-hand punch             |
| 0x0020  | `DoubleSlash`      | Rapier / scimitar style                |
| 0x0040  | `TripleSlash`      | Quickness / fast swords                |
| 0x0080  | `DoubleThrust`     | Stiletto style                         |
| 0x0100  | `TripleThrust`     | Spears / jambiya                       |
| 0x0200  | `OffhandThrust`    |                                        |
| 0x0400  | `OffhandSlash`     |                                        |
| 0x0800  | `OffhandDoubleSlash`|                                       |
| 0x1000  | `OffhandTripleSlash`|                                       |
| 0x2000  | `OffhandDoubleThrust`|                                      |
| 0x4000  | `OffhandTripleThrust`|                                      |

Composites: `Unarmed = Punch|Kick|OffhandPunch`;
`DoubleStrike`, `TripleStrike`, `MultiStrike = Double|Triple`;
`Offhand = <all offhand bits>`;
`Thrusts`, `Slashes`, `Punches` (the single-technique groupings).

**How a weapon picks its animation.** `WorldObject.GetAttackType(stance,
powerLevel, offhand)` (ACE `WorldObject_Weapon.cs:1050`):

1. If `offhand` is true, return the offhand variant.
2. Start from the weapon's `W_AttackType` bitfield.
3. Apply stance-specific overrides. The key rule: many weapons have both
   Thrust and Slash. The **ThrustThreshold** (= `0.33f`, sourced from the
   Dark Majesty Strategy Guide p.150) gates which one is used:
   ```
   powerLevel <  0.33 → use Thrust
   powerLevel >= 0.33 → use Slash
   ```
   Some weapons use `DoubleThrust|DoubleSlash` or
   `TripleThrust|TripleSlash` pairs; the threshold still applies.
4. Shield stance forces thrust on multi-strike weapons; sword stance
   (no shield) forces slash.

**Multi-strike to single-strike reduction.** `AttackType.ReduceMultiStrike`
collapses `Double/Triple{Thrust,Slash}` → `{Thrust,Slash}` for the
damage-type decision, because multi-strike only affects the animation
loop and swing count, not what body-parts it hits or what damage type
it applies.

**GetNumStrikes.** Returns 1, 2, or 3 based on the flag. The player's
`Attack()` function divides the anim length by numStrikes and fires a
`DamageTarget()` call per sub-strike (each one rolling its own damage,
its own crit, its own evasion). One power-bar commit = N damage rolls.

**From the decompiled client side** (`chunk_00560000.c:8186`), the
formatted hit message is literally:

```c
FUN_00406500(&puStack_58, "You %s %s for %d point%s of %sdamage!",
             verb, targetName, damage, pluralS, damageTypeStr);
```

Where `verb` is produced from `local_50 + 5` — a table keyed off attack
type (e.g. "slash", "thrust", "pierce"). So the retail HUD verbifies the
attack type.

---

## 3. Attack heights (AttackHeight enum)

From `AttackHeight.cs`:

```
High   = 1
Medium = 2
Low    = 3
```

The client sends this as a `uint32` in both melee and missile attack
packets. **Its effect on damage** flows through *body-part selection*,
not a direct damage multiplier. The flow:

1. Retail builds a **Quadrant** by OR-ing the height with the attacker's
   relative direction:
   ```
   quadrant = attackHeight.ToQuadrant() | attacker.GetRelativeDir(defender);
   ```
2. The defender's `BodyPartTable` has 12 buckets, one per quadrant:
   `[HLF MLF LLF | HRF MRF LRF | HLB MLB LLB | HRB MRB LRB]`
   (where H/M/L = High/Medium/Low, L/R = Left/Right, F/B = Front/Back).
3. Each creature weenie's `PropertiesBodyPart` has per-quadrant
   probabilities; `BodyPartTable.RollBodyPart(quadrant)` weighted-random
   selects one.
4. The selected `CombatBodyPart` has its own armor and its own crit
   multiplier.

**For player defenders** (the retail client fills this in differently)
the flow is simpler because player body-parts are not in a full weenie
table; `BodyParts.GetBodyPart(AttackHeight)` picks uniformly from:

```
High   → Head, Chest, UpperArm
Medium → Chest, Abdomen, UpperArm, LowerArm, Hand, UpperLeg
Low    → Foot, LowerLeg
```

The selected body part then maps to a `CoverageMask` which selects which
armor/clothing pieces resist the hit. Medium attacks hit the meatiest
torso zones and therefore usually connect with chest armor, which is
typically the best-AL piece — this is *why* players who want to mitigate
incoming damage should buff chest pieces. Low attacks hit feet/legs
which often have lower AL — **low attacks are typically more damaging**
on well-geared players.

**On monsters,** height × quadrant distributes across 27 possible
`CombatBodyPart` values (Head, Chest, Abdomen, UpperArm, LowerArm, Hand,
UpperLeg, LowerLeg, Foot, Horn, FrontLeg, FrontFoot, RearLeg, RearFoot,
Torso, Tail, Arm, Leg, Claw, Wings, Breath, Tentacle, UpperTentacle,
LowerTentacle, Cloak — enum `CombatBodyPart`). Different monsters have
different coverage; a drudge has limbs, an olthoi has mandibles + abdomen
+ legs. The weenie table encodes this.

---

## 4. Power bar / accuracy bar

**The power bar is a user-charged attack intensity meter**, 0.0 → 1.0.
The client holds the attack key to fill it; releasing fires an attack
packet carrying the current value in the `Power` or `Accuracy` float
field. Player_Melee.cs:

```csharp
public float PowerLevel   { get; set; }   // 0.0..1.0
public float AccuracyLevel{ get; set; }   // 0.0..1.0 (bows)

public float GetPowerAccuracyBar() =>
    GetCombatType() == CombatType.Missile ? AccuracyLevel : PowerLevel;

public PowerAccuracy GetPowerRange() =>
    PowerLevel < 0.33f ? Low :
    PowerLevel < 0.66f ? Medium :
                         High;
```

**How long does it take to fill?** ACE's refill model:
`NextRefillTime = now + PowerLevel * refillMod` after an attack lands,
where `refillMod = 0.8f` for dual-wield (20% faster) else 1.0. Retail
uses approximately 1 second for full power from a hard release; charging
to 50% bar = ~0.5s, full = ~1.0s. The exact tick rate is handled by the
**client's** UI animation; the server only sees the final `PowerLevel`
float the client sends and trusts it (this is why power-bar-hacks
historically existed — no server-side verification).

### Effect of power on damage math

Player_Combat.cs:

```csharp
public override float GetPowerMod(WorldObject weapon)
{
    if (weapon == null || !weapon.IsRanged)
        return PowerLevel + 0.5f;   // → [0.5, 1.5] range for melee/thrown
    else
        return 1.0f;                // melee power-bar inert for bows
}

public override float GetAccuracyMod(WorldObject weapon)
{
    if (weapon != null && weapon.IsRanged)
        return AccuracyLevel + 0.6f;// → [0.6, 1.6] range for bows
    else
        return 1.0f;
}
```

So the **effective multiplier ranges**:

- **Melee / thrown:** `PowerMod ∈ [0.5, 1.5]`, applied as a *damage*
  multiplier. Zero-bar does half damage, full-bar does 1.5×.
- **Bows / crossbows / atlatls:** `AccuracyMod ∈ [0.6, 1.6]`, applied
  as an *attack-skill* multiplier (makes you more likely to *hit*, not
  hit harder). Bow damage is power-bar-inert; bows have a separate
  `DamageMod` attribute on the launcher (e.g. Yumi = 2.13).

**Stamina cost.** `Player.GetAttackStamina(PowerAccuracy)` from
`Player_Combat.cs:621` interpolates a cost from a 3×3 table keyed by
bucket (Low/Med/High) and held-item burden, then scales down by an
Endurance bonus (caps at 50% reduction at ~290 Endurance). Low-bar
thrust costs 1 stam per 700 burden; high-bar full swing costs 2 per 700,
4 per 1200, 6 per 1600. Running out of stamina drops defense to 0 AND
halves your weapon skill.

---

## 5. Damage formula — the full retail expression

**Canonical form, exactly as implemented in `DamageEvent.DoCalculateDamage`:**

```
// PHASE 1 — hit?
evasionChance = 1 - SkillCheck.GetSkillChance(
                        effectiveAttackSkill,
                        effectiveDefenseSkill,
                        0.03f);
if (rng() < evasionChance) → EVADE, damage = 0, exit.

// PHASE 2 — base damage roll
baseDamage = rng(weapon.MinDamage, weapon.MaxDamage)  // uniform

// PHASE 3 — pre-mitigation multipliers
damageBefore =
    baseDamage
  * attributeMod                        // [1.0 … 5.5] based on Str/Coord
  * powerMod                            // [0.5 … 1.5] melee, 1.0 missile
  * slayerMod                           // 1.0 base, ×bonus vs slayer type
  * damageRatingMod                     // additive combine (see §6)

// PHASE 4 — CRITICAL override (rolled after non-crit compute)
if (rng() < criticalChance) {
    damageBefore = weapon.MaxDamage
                 * attributeMod
                 * powerMod
                 * slayerMod
                 * damageRatingMod     // recomputed, no Recklessness in crits
                 * criticalDamageMod;   // 1 + wepMultiplier
}

// PHASE 5 — mitigation
damage = damageBefore
       * armorMod                      // AL → 200/3 / (AL + 200/3)
       * shieldMod                     // shield SL absorption
       * resistanceMod                 // natural + buff resists
       * damageResistanceRatingMod;    // DRR from augmentations

return round(damage);
```

Every factor is a multiplicative `float`, applied in order. `AdditiveCombine`
is used **within** the `damageRatingMod` slot — that's where DR, recklessness,
sneak attack, and heritage stack additively (see §6). Everything else is
pure multiplication.

### Component details

**`baseDamage`** — `BaseDamageMod` on the weapon wraps a `BaseDamage(max,
variance)` plus enchantments. Actual min = `max × (1 − variance × VarianceMod)`.
`ThreadSafeRandom.Next(min, max)` uniformly samples. Blood Drinker
enchantments add a flat `DamageBonus`. Missile launchers (bows) add
`ElementalBonus` (additive int) when the ammo damage type matches the bow's
imbued element.

**`attributeMod`** — from `SkillFormula.GetAttributeMod`:
```csharp
public const float DefaultMod = 0.011f;   // melee, finesse, thrown, atlatl
public const float BowMod     = 0.008f;   // bows, crossbows

attributeMod = max(1.0f, 1.0f + (currentSkill - 55) * factor)
```
Note: despite the name this is *not* a direct attribute stat — it's the
current weapon skill being damage-scaled. The name comes from the fact
that in pre-MoA AC it WAS tied to the primary attribute. Post-MoA, it's
the combat skill. A 400 Heavy Weapons skill gives
`1 + (400-55)*0.011 = 1 + 3.795 = 4.795`. A 200 bow skill gives
`1 + 145*0.008 = 2.16`.

**`powerMod`** — see §4. `PowerLevel + 0.5` melee, `1.0` missile.

**`slayerMod`** — `WorldObject.GetWeaponCreatureSlayerModifier`. If the
weapon has a `SlayerCreatureType` matching the target's `CreatureType`,
returns the stored `SlayerDamageBonus` (values like 1.5–3.0 in data). Else
1.0.

**`damageRatingMod`** — `Creature.AdditiveCombine(damageRatingBaseMod,
recklessnessMod, sneakAttackMod, heritageMod [, pkDamageMod])`. Each
input is something like `1.xx`, and `AdditiveCombine` does:
```
(x1 − 1) + (x2 − 1) + … + 1
```
i.e. additive-in-delta. So `1.10 × 1.20` ≠ `AdditiveCombine(1.10, 1.20)
= 1.30`.

**`armorMod`** — see §9.

**`shieldMod`** — `GetShieldMod`. If the attacker is within the shield's
effective arc (default 180° front cone), compute
`effectiveSL = baseSL + impenMods`,
`effectiveRL = baseRL + baneMods` (clamped [-2, +2]),
`effectiveLevel = effectiveSL × effectiveRL`, capped at Shield skill
(`skill.Current` if specialized, else `skill.Current / 2`). Final
`shieldMod = SkillFormula.CalcArmorMod(effectiveLevel)`.

**`resistanceMod`** — §10.

**`damageResistanceRatingMod`** — rating-based mitigation from
augmentations/buffs/imbues, converted via `GetNegativeRatingMod`.

### Worked examples

**Example A — low-level Chorizon, swords, full bar, hits a drudge:**

- Heavy Weapons skill 125, Strength 70, no enchantments
- Weapon: Iron Sword (max 12, variance 0.5 → min 6)
- Target: Drudge Skulker — BaseArmor 24 on chest, natural 0 resists
- PowerLevel 1.0, no crit, no aug DR, front-left medium quadrant hit

Steps:
1. `baseDamage = rng(6, 12) = 9` (sample)
2. `attributeMod = 1 + (125 − 55)×0.011 = 1 + 0.77 = 1.77`
3. `powerMod = 1.0 + 0.5 = 1.5`
4. `slayerMod = 1.0`, `damageRatingMod = 1.0`
5. `damageBefore = 9 × 1.77 × 1.5 × 1.0 × 1.0 = 23.895`
6. Drudge chest: `effectiveAL = 24 × 1.0 = 24`, `armorMod =
   (200/3) / (24 + 200/3) = 66.67 / 90.67 = 0.735`
7. `resistanceMod = 1.0` (drudge: slash neutral)
8. `damage = 23.895 × 0.735 × 1.0 × 1.0 × 1.0 = 17.56`
9. Final: `18 damage (slash)`

**Example B — mid-level with crit:**

- Light Weapons skill 300, Coordination 120, Biting Strike rapier
- Weapon: Bone Rapier (max 32, variance 0.4, CritFrequency 0.15)
- Target: Banderling — BaseArmor 50 on chest, 1.3× pierce vulnerability
- PowerLevel 0.3 → thrust, crit rolls TRUE

Steps:
1. `attributeMod = 1 + (300 − 55)×0.011 = 1 + 2.695 = 3.695`
2. `powerMod = 0.3 + 0.5 = 0.8`
3. On crit, `baseDamage` = weapon max = 32 (not rolled)
4. `critDmgMod = 1.0 + 1.0 = 2.0` (default weapon multiplier)
5. `damageBefore = 32 × 3.695 × 0.8 × 1.0 × 1.0 × 2.0 = 189.1`
6. Banderling chest: `effectiveAL = 50`, `armorMod = 66.67 / 116.67 =
   0.571`
7. `resistanceMod = 1.3` (pierce vuln)
8. `damage = 189.1 × 0.571 × 1.3 × 1.0 = 140.4`
9. Final: `140 damage (pierce)` — "Critical hit!"

**Example C — bow vs naked PvP target:**

- Missile Weapons skill 350, Coord 160
- Weapon: Yumi (max 45, var 0.3, DamageMod 2.13 → "max" = 45 × 2.13 = 96)
- Ammo: Cold-imbued arrows (+20 elemental bonus)
- Target: player, 0 natural Cold resist, Prot Cold 6 (res 0.56)
- AccuracyLevel 1.0, no crit, no sneak, NPK so no PK scale

Steps:
1. `maxDamage = (45 + 0 + 20) × 2.13 = 138.45`, `minDamage = 138.45 ×
   0.7 = 96.9`
2. `baseDamage = rng(96.9, 138.45) = 118` sampled
3. `attributeMod = 1 + (350 − 55)×0.008 = 1 + 2.36 = 3.36`
4. `powerMod = 1.0` (bows ignore power bar for damage)
5. `damageBefore = 118 × 3.36 × 1.0 × 1.0 × 1.0 = 396.5`
6. `attackSkill = skill × accuracyMod × offenseMod = 350 × 1.6 × 1.0 =
   560` (accuracy boosts *hit chance*, not damage)
7. Target chest clothes only: `effectiveAL = 12 + 0 = 12`, `armorMod =
   66.67 / 78.67 = 0.847`
8. `resistanceMod = 0.56` (Prot Cold 6)
9. `damage = 396.5 × 0.847 × 0.56 = 188.1`
10. Final: `188 damage (cold)`

---

## 6. Critical hits

**Base crit chance:**

- **Physical:** 10% (`defaultPhysicalCritFrequency = 0.1f` in
  `WorldObject_Weapon.cs:291`).
- **Magic:** 5% (`defaultMagicCritFrequency = 0.05f`, post-Iron Coast
  release; was 2% pre-Atonement).

Per-weapon override via `PropertyFloat.CriticalFrequency` (e.g.
Biting Strike quest weapons 15%).

**Critical Strike imbue:** `Math.Max(critRate, GetCriticalStrikeMod(skill))`
— a scaling function that rewards high attack skill. At low skill it's
10%, at 400+ skill it can push physical critical chance past 30%.

**Crit Rating:** `critRate += wielder.GetCritRating() * 0.01f`. Each
point of Crit Rating = +1% flat.

**Crit Resist Rating:** mitigates incoming crits as a **multiplicative
reduction** via `GetNegativeRatingMod` — rating 20 → critRate ×= 0.833.

**Critical damage multiplier:** `CriticalDamageMod = 1.0 +
weapon.CriticalMultiplier` where `CriticalMultiplier` defaults to
`1.0f` (so default crit is 2× max damage). Crippling Blow imbue
replaces this when higher via `Math.Max`.

**Crit damage bookkeeping:**

1. On crit, `DamageBeforeMitigation = weapon.MaxDamage × attributeMod ×
   powerMod × slayerMod × damageRatingMod × critDamageMod`. Note
   baseDamage is replaced by **max** damage, not re-rolled.
2. Recklessness is **set to 1.0** on crits (doesn't stack with crit
   multiplier — retail design decision explicit in the code comment
   `// recklessness excluded from crits`).
3. `CriticalDamageRatingMod` (from Crit Damage Rating augmentation)
   replaces Recklessness in the rating stack for the crit calculation.

**Critical Defense augmentation:** if the defender has the augmentation
and a crit is rolled, there's a secondary roll:
```
criticalDefenseChance = augRank * 0.05f    // vs player attacker
criticalDefenseChance = augRank * 0.25f    // vs monster attacker
```
If that succeeds, `CriticalDefended = true`; the attacker still hits
but with normal (non-crit) damage, and the AttackConditions bit 0x01 is
set. The hit message adds: `"Your target's Critical Protection
augmentation allows them to avoid your critical hit!"` (the literal
string lives at 0x00560000.c:8191).

**Logoff auto-crit:** if the defender is logging out OR in a PK logout
freeze window (2 min post-PvP), `criticalChance = 1.0f`. From the dev
notes (ACE comments cite 2002/08 Atonement): "any time a character is
logging off, PK or not, all physical attacks against them become
automatically critical. (Note that spells do not share this behavior.)"

---

## 7. Defense formula — attack skill vs defense skill

The core mechanic uses a **logistic curve** on skill difference.
`SkillCheck.GetSkillChance`:

```csharp
public static double GetSkillChance(int skill, int difficulty,
                                    float factor = 0.03f)
{
    var chance = 1.0 - (1.0 / (1.0 + Math.Exp(factor * (skill - difficulty))));
    return Math.Clamp(chance, 0.0, 1.0);
}
```

- **Physical combat:** `factor = 0.03f`. Equal skill = 50% hit. +50 skill
  advantage → ~81.8% hit. +100 → ~95.3%. +200 → ~99.75%. −50 skill →
  ~18%. −100 → ~4.7%.
- **Magic:** `factor = 0.07f` — the same curve but **steeper**. Equal
  skill is still 50% resist, but +50 skill advantage is already 97%.
  This is why magic defense drops off fast when underskilled.

**EvadeChance** = `1 − hitChance`. If `rng() < EvadeChance`, the attack
is evaded (no damage, no stamina cost to attacker, 1 stamina cost to
defender in combat mode).

### Effective skills

**Attacker (player) effective attack skill:**
```
effAttack = round(weaponSkill * accuracyMod * offenseMod)
```
- `weaponSkill` = current skill (post-enchantments) of the equipped
  weapon's skill category (LightWeapons / HeavyWeapons / FinesseWeapons
  / MissileWeapons / TwoHandedCombat / DualWield).
- `accuracyMod` = `AccuracyLevel + 0.6` for bows, else 1.0.
- `offenseMod` = weapon enchantment like Heart Seeker's attack-skill
  mod (e.g. 1.12 for HS VII).

Off-hand attacks: if `DualWield.Current < weapon.Current`, the DualWield
skill replaces the weapon skill. (That's why specialized dual-wield
builds spec DW — you don't want your main-hand skill to leak down into
off-hand hits.)

**Attacker (monster) effective attack skill:**
```
effAttack = round(weaponSkill * offenseMod)    // no accuracy mod
```

**Defender effective defense skill:**
```
effDefense = round(defenseSkill * defenseMod * burdenMod * stanceMod
                   + defenseImbues)
```
- `defenseSkill` = MeleeDefense vs melee/missile-close-range,
  MissileDefense vs missile, MagicDefense vs spells.
- `defenseMod` = Defender weapon enchantment (e.g. Defender V = 1.25).
- `burdenMod` = scales with encumbrance-vs-capacity.
- `stanceMod` from `Player.GetDefenseStanceMod()`:
  - `IsJumping` → 0.5
  - `IsLoggingOut` → 0.8
  - In combat mode (not NonCombat) → 1.0
  - NonCombat + Crouch → 0.4
  - NonCombat + Sitting → 0.3
  - NonCombat + Sleeping → 0.2
- `defenseImbues` = count of equipped items with the matching
  MeleeDefense/MissileDefense imbued effect (flat additive).

**Exhausted (Stamina ≤ 0):** `effDefense = 0` → always hit. Also attacker
weapon skill is halved internally. Stamina management **is** a combat
mechanic.

**Overpower.** Some elite monsters (olthoi queens, Virindi lords) have
an `Overpower` property. On attack, an Overpower roll happens **before**
the evasion roll: if successful, the hit is auto-landed regardless of
defense skill. Two formulas exist (`OverpowerMethod` toggle);
Formula B (default in ACE) is:
```
overpowerChance   = attacker.Overpower ?? 0  (percent, 0-100)
resistChance      = defender.OverpowerResist ?? 0
final = rng() < overpowerChance*0.01
        AND rng() >= resistChance*0.01
```
If overpowered, `AttackConditions.Overpower` flag is set and the
attacker notification reads "Overpower!".

---

## 8. Body-part targeting + per-part AL

### Player body parts (9 parts)

From `BodyPart.cs`:

| CombatBodyPart | BodyPart flag | DamageLocation (wire) | Height pool |
|----------------|---------------|-----------------------|-------------|
| Head           | 0x001         | 0x0                   | High        |
| Chest          | 0x002         | 0x1                   | High, Mid   |
| Abdomen        | 0x004         | 0x2                   | Mid         |
| UpperArm       | 0x008         | 0x3                   | High, Mid   |
| LowerArm       | 0x010         | 0x4                   | Mid         |
| Hand           | 0x020         | 0x5                   | Mid         |
| UpperLeg       | 0x040         | 0x6                   | Mid         |
| LowerLeg       | 0x080         | 0x7                   | Low         |
| Foot           | 0x100         | 0x8                   | Low         |

The **High pool is 3 parts**, **Mid is 6 parts**, **Low is 2 parts**.
This is a meaningful targeting choice: High has 1/3 chance of head
(typically best helmet AL), Low has 50% chance of foot (commonly one
of the weakest pieces on an ungeared player).

### Monster body parts (27 parts, per-weenie table)

`CombatBodyPart` enum values 0..26 include generic humanoid parts
(Head/Chest/Abdomen/UpperArm/LowerArm/Hand/UpperLeg/LowerLeg/Foot) plus
monster-specific (Horn, FrontLeg, FrontFoot, RearLeg, RearFoot, Torso,
Tail, Arm, Leg, Claw, Wings, Breath, Tentacle, UpperTentacle,
LowerTentacle, Cloak). Not every creature has every part — the weenie's
`PropertiesBodyPart` dictionary holds only the parts it has, each with:

- `BaseArmor` (int) — AL for that part
- `HLF, MLF, LLF, HRF, MRF, LRF, HLB, MLB, LLB, HRB, MRB, LRB` (float) —
  per-quadrant hit probability (most are 0.0, a few non-zero per part)
- `ArmorVsSlash, ArmorVsPierce, ..., ArmorVsNether` (int) — the
  per-damage-type resist scaling (multiplied into BaseArmor)

A drudge skulker's torso dominates the MLF/MRF/MLB/MRB cells; its head
appears only in HLF/HRF/HLB/HRB. Creature armor is per-part, not global.

### Per-part AL + resistance scaling

`Creature_BodyPart.GetEffectiveArmorVsType`:

```csharp
var armorVsType  = biota.BaseArmor * (float)Creature.GetArmorVsType(damageType);
var enchantMod   = ignoreMagicResist ? 0 : EnchantmentManager.GetBodyArmorMod();
var effectiveAL  = armorVsType + enchantMod;
foreach (var armorLayer in armorLayers)
    effectiveAL += GetArmorMod(armorLayer, damageType, ignoreMagicArmor);
if (effectiveAL > 0)
    effectiveAL *= armorRendingMod;   // 1.0 unless ArmorRending imbue
return effectiveAL;
```

For players, the `armorLayers` are the actual equipped armor/clothing
pieces whose `ClothingPriority` covers the hit body part — so a chest
piece + a surcoat + an undershirt all stack. `Clothing.GetArmorMod()`
per layer:

```
effectiveAL_piece = piece.BaseArmorLevel + impenAdditive
effectiveRL_piece = piece.ResistanceVsType + baneAdditive
effectiveRL_piece = clamp(effectiveRL_piece, -2.0, +2.0)
layerAL = effectiveAL_piece * effectiveRL_piece
```

Sum across layers → final effective AL → `SkillFormula.CalcArmorMod`.

---

## 9. Damage types + resistances

### 7 damage types + 4 special

`DamageType.cs`:

| Flag       | Value      | Notes                                     |
|------------|------------|-------------------------------------------|
| Slash      | 0x001      | Physical                                  |
| Pierce     | 0x002      | Physical                                  |
| Bludgeon   | 0x004      | Physical                                  |
| Cold       | 0x008      | Elemental                                 |
| Fire       | 0x010      | Elemental                                 |
| Acid       | 0x020      | Elemental                                 |
| Electric   | 0x040      | Elemental                                 |
| Health     | 0x080      | Drain (harm)                              |
| Stamina    | 0x100      | Drain                                     |
| Mana       | 0x200      | Drain                                     |
| Nether     | 0x400      | Void magic                                |
| Base       | 0x10000000 | Prismatic arrow sentinel                  |

Helpers: `Physical = Slash|Pierce|Bludgeon`, `Elemental =
Cold|Fire|Acid|Electric`.

**Multi-damage weapons.** Many weapons have `Slash|Pierce` or similar
bitfields. The selection process in `Player.GetDamageType`:

1. If the weapon has a single damage type → use it.
2. If `DamageType.Slash|Pierce`:
   - Unarmed: low power → Pierce, high power → Slash
   - Thrust attack → Pierce
   - Otherwise → Slash
3. Other multi-bit → `SelectDamageType(powerLevel)`:
   - If `powerLevel < 0.33`, bias to Physical; else bias to Elemental.
   - Randomly pick from the resulting subset.

This is how **pyreal (ivory-blade) weapons** and **Aerfalle-style
elemental weapons** work — the power bar biases whether you do physical
or elemental on a given swing.

### Resistances

Each creature has per-damage-type `ResistXxxMod` (float, 1.0 default):

```
ResistSlash, ResistPierce, ResistBludgeon,
ResistFire,  ResistCold,   ResistAcid,   ResistElectric,
ResistNether,
ResistHealthBoost, ResistStaminaDrain, ResistManaDrain
```

A value of `0.5` = 50% reduction (Prot 5), `2.0` = 100% bonus damage
(Vuln 6). The clamp is [-2, +2].

### Natural resistances (players only)

Retail **April 2002 Betrayal patch**: player Str+End combination grants
a passive resistance to the 7 damage types, capping at 50% reduction
equivalent to Life Prot V. From `Player_Combat.cs:GetNaturalResistance`:

```csharp
var strAndEnd = Strength.Base + Endurance.Base;
if (strAndEnd <= 200)
    return 1.0f;
var natRes = 1.0f - (float)(strAndEnd - 200) / 300 * 0.5f;
return Math.Max(natRes, 0.5f);
```

Tiers (for the UI description):
- ≤200: None
- 201–260: Poor (1–10% reduction)
- 261–320: Mediocre
- 321–380: Hardy
- 381–440: Resilient
- 441+: Indomitable (cap, 50% reduction)

**Crucial detail:** natural resists do NOT stack with Life Protection
spells. A higher-rank Prot spell **overwrites** natural resists. However,
vulns subtract against the prot; if the net is worse than natural, you
still don't go below natural. This is why some low-end characters are
tougher than you'd expect.

**Nether exception:** All creatures "under Asheron's protection" take
50% damage from Nether damage by default (fandom: Anniversary-patch
announcement). `GetNaturalResistance(DamageType.Nether)` hardcodes 0.5.

### Final resistance math

`ResistanceMod = playerDefender.GetResistanceMod(damageType, attacker,
weapon, weaponResistanceMod)`. Composed of:

```
naturalRes  = GetNaturalResistance(damageType)  // 0.5..1.0
prot/vuln   = EnchantmentManager.GetResistanceMod(damageType)
              × weaponResistanceMod              // weapon rend / anti-mod
final       = min(naturalRes, prot)  AND  × vuln effects
```

The exact `min`/`max` logic is subtle; the simplified version is: if the
life-prot is stronger than natural, prot wins; else natural wins (this
is what the April 2002 announcement said). Vulns cancel prots in full
before falling back to natural.

---

## 10. Wire format

### Client → Server (C2S GameActions)

All these wrap in the `GameAction` (0xF7B1) envelope over the
ordered/reliable message stream. Payload layout:

**Targeted Melee Attack — GameAction 0x0008:**
```
uint32  ObjectId        // target guid
uint32  Height          // AttackHeight enum: 1=H, 2=M, 3=L
float32 Power           // [0.0, 1.0], clamped server-side
```
Total payload: 12 bytes (after the GameAction header).

**Targeted Missile Attack — GameAction 0x000A:**
```
uint32  ObjectId
uint32  Height
float32 Accuracy        // [0.0, 1.0]
```

**Change Combat Mode — GameAction 0x0053:**
```
uint32 Mode             // CombatMode enum
```

**Cancel Attack — GameAction 0x01B7:**
```
(empty body)
```

### Server → Client (S2C GameEvents wrapped in 0xF7B0 Ordered GameEvent)

**AttackerNotification (0x01B3) — "You hit X for N damage!":**
```
string16L DefenderName        // 2-byte length, UTF-16LE bytes, dword-aligned
uint32    Type                // DamageType
float64   DamagePercent       // fraction of defender MaxHealth, 0..1
uint32    Damage              // actual damage applied
uint32    Critical            // 0 or 1 (really a bool, written as uint32)
uint32    AttackConditions    // AttackConditionsMask bits
(align to 4 bytes)
```
Max observed length ~76 bytes.

**DefenderNotification (0x01B5) — "The monster hits you!":**
```
string16L AttackerName
uint32    Type                // DamageType
float64   DamagePercent
uint32    Damage
uint32    Location            // DamageLocation (0..8, player-only)
uint32    Critical
uint32    AttackConditions
(align to 4)
```
Max ~80 bytes.

**EvasionAttackerNotification (0x01B8):**
```
string16L DefenderName        // "X evaded your attack."
```

**EvasionDefenderNotification (0x01B6):**
```
string16L AttackerName        // "You evaded X's attack."
```

**AttackDone (0x01B4):**
```
uint32 Number                 // appears unused by client; WeenieError in ACE
```

**CommenceAttack (0x01B7):**
```
(empty body, just hourglass start for repeat attacks)
```

**PlayerDeathEvent (S2C direct 0x019E, not a GameEvent):**
```
string16L Message             // Death message ("X killed you in battle!")
uint32    KilledId
uint32    KillerId
```

**VictimNotificationSelf / Other (GameEvent):**
```
string16L Message             // death message for victim (self) or bystander (other)
```

### AttackConditions bit layout

From the retail client decompilation (`chunk_00560000.c:8173-8201`)
confirming bit meanings:

```c
if (param_6 != 0)   "Critical hit!  "              // the `critical` param, separate
if ((uVar2 & 8) != 0) "Overpower! "
if ((uVar2 & 4) != 0) "Sneak Attack! "
if ((uVar2 & 2) != 0) "Recklessness! "
if ((uVar2 & 1) != 0) " Your target's Critical Protection augmentation allows them to avoid your critical hit!"
```

So the bits are exactly:

| Bit  | Meaning                                      |
|------|----------------------------------------------|
| 0x01 | CriticalProtectionAugmentation (critical defended) |
| 0x02 | Recklessness active                          |
| 0x04 | Sneak Attack triggered                       |
| 0x08 | Overpower triggered                          |

(ACE's `AttackConditions.cs` and Chorizite's `AttackConditionsMask` both
match this, modulo Chorizite missing the Overpower bit in its generated
enum — an oversight to correct in acdream.)

---

## 11. PvP vs PvE differences

### PK status flags

`PlayerKillerStatus`:
- `NPK` — non-PK, cannot attack or be attacked by players
- `PK` — full player killer, can attack other PKs
- `PKLite` — PK-Lite, non-lethal PvP (no corpse drop on death to other PKL)
- `Free` — special (event/gladiator), can attack anyone
- `NPK_Protected` — post-revive grace period

### Attack gating

`Player.CheckPKStatusVsTarget(target, spell)`:

1. Either side `Free` → allowed.
2. `NPK` attacker with harmful spell on player → reject
   (`WeenieError.YouFailToAffect_YouAreNotPK`).
3. `NPK` defender → reject
   (`WeenieError._FailsToAffectYou_TheyAreNotPK`).
4. Different PK types (PK vs PKL, etc.) → reject with
   `NotSamePKType` unless it's a beneficial spell on NPK.
5. Housing permission check: `CheckHouseRestrictions` — attacks across
   restricted-house cell boundaries denied.

Mismatched PK type on a monster target (e.g. a PK quest-monster vs NPK
attacker) also rejects.

### PK damage adjustments

In PvP:
- `PkDamageMod = Creature.GetPositiveRatingMod(attacker.GetPKDamageRating())`
  added to the damage rating stack.
- `PkDamageResistanceMod = Creature.GetNegativeRatingMod(defender.
  GetPKDamageResistRating())` added to damage resistance stack.
- PvP elemental damage bonus is **halved**:
  ```csharp
  if (modifier > 1.0f && target is Player)
      modifier = 1.0f + (modifier - 1.0f) * 0.5f;
  ```

### PK timers

- `LastPkAttackTimestamp` — updated on successful PvP hit (both sides).
- `pk_timer` property (typically 30s) — while active, cannot logout
  normally, spawns are gated.
- `PKLogoffTimer = 2 minutes` — logout freeze window. During this window
  all physical hits auto-crit against you.

### Lifestone protection

5 min invulnerability window after lifestone tie; `UnderLifestoneProtection`.
Any PvP attack dispels it on the attacker (not the defender). Server sends
`"The Lifestone's magic protects X from the attack!"` (FUN_00570000:2431
in the decompile, verbatim). Damage = 0, `LifestoneProtection` bit set.

---

## 12. Port plan for acdream

acdream is the **client**, so our combat layer splits into:

1. **Client-authoritative UI/prediction** — power bar widget, attack
   staging, send attack GameActions, play swing animation while waiting.
2. **Wire decoder** — parse inbound `AttackerNotification`,
   `DefenderNotification`, `EvasionNotification`, `AttackDone`,
   `PlayerDeathEvent` and surface them to the plugin API + chat/HUD.
3. **Shadow damage calculator** — our own implementation of the ACE
   formulas, used to (a) feed the power-bar damage preview, (b) sanity
   check server damage (for anti-cheat debug output, never used as
   authority), (c) give plugins access to "if I attacked with X, how
   much would I do?" queries.

### Suggested namespace layout

```
acdream.Combat/
  CombatMode.cs            // enum: Undef|NonCombat|Melee|Missile|Magic
  AttackType.cs            // enum Flags, same bits as ACE
  AttackHeight.cs          // enum: High|Medium|Low + ToQuadrant()
  AttackConditions.cs      // enum Flags, 0x01/0x02/0x04/0x08
  DamageType.cs            // enum Flags, same bits as ACE
  CombatBodyPart.cs        // enum, 0..26
  DamageLocation.cs        // enum, 0..8 (player wire)
  Quadrant.cs              // enum Flags, High|Mid|Low|Left|Right|Front|Back

  AttackRequest.cs         // POCO: TargetGuid, Height, Power, Kind (Melee|Missile)
  AttackResult.cs          // POCO: Hit|Evade|Lifestone; DamageType; Damage;
                           //       Critical; BodyPart; AttackConditions
  DamageEvent.cs           // POCO mirroring ACE's DamageEvent for shadow calc

  CombatMath.cs            // static helpers: GetSkillChance, CalcArmorMod,
                           //   GetAttributeMod, AdditiveCombine,
                           //   GetPositiveRatingMod, GetNegativeRatingMod,
                           //   ThrustThreshold const 0.33f,
                           //   defaultPhysicalCritFrequency 0.1f,
                           //   defaultMagicCritFrequency 0.05f,
                           //   DefaultMod 0.011f, BowMod 0.008f,
                           //   ArmorMod 200f/3f
  DefenseBuild.cs          // struct: attack/defense skills, mods,
                           //   weapon, stance, burden — for shadow calc
  ArmorLayer.cs            // struct: BaseAL, ResVsType map, Impen, Bane
  BodyPartTable.cs         // 12-quadrant probability table loader (from weenies)

  Wire/
    CombatC2S.cs           // TargetedMeleeAttack / MissileAttack /
                           //   ChangeCombatMode / CancelAttack senders
    CombatS2C.cs           // AttackerNotification / DefenderNotification /
                           //   Evasion* / AttackDone / PlayerDeath decoders

  CombatClient.cs          // high-level: HandleAttackKey(), charge tick,
                           //   release, process incoming notification,
                           //   emit events to plugin API
```

### Conformance tests

Port these from ACE's test suite plus capture direct pcaps from retail:

- `SkillCheckTests.cs` — verify the logistic curve at ±50/100/200 skill
  delta, factor 0.03 and 0.07.
- `SkillFormulaTests.cs` — already exists in ACE
  (`references/ACE/Source/ACE.Server.Tests/SkillFormulaTests.cs`),
  port the cases verbatim. `GetAttributeMod` at 55/100/200/400 skill,
  bow and non-bow factors. `CalcArmorMod` at AL 0, 50, 100, 200, 500,
  and negative values.
- `DamageEventTests.cs` — table-driven worked examples A, B, C from §5
  here, with fixed RNG seeds to ensure exact reproducibility.
- `BodyPartTableTests.cs` — given a weenie's PropertiesBodyPart dict,
  verify all 12 quadrants sum probabilities correctly and that
  `RollBodyPart` is deterministic with a fixed seed.
- `AttackWireTests.cs` — byte-level round-trip tests against canned
  hex payloads. Start from `AttackerNotification` with a known
  DamageType/Damage/Critical/AttackConditions combination, ensure our
  encoder produces the exact bytes the retail client expects, and our
  decoder extracts the same values.

### Phase ordering (proposed)

| Phase label | What ships |
|-------------|------------|
| R2.A Wire  | Enums + C2S senders + S2C decoders, 1:1 with Chorizite types. Integration into WorldSession. HUD shows "You hit X for N" chat messages. No shadow math yet. |
| R2.B Power | Power bar widget, charging, release → `Combat_TargetedMeleeAttack`. `GetPowerRange` mapping, ThrustThreshold logic. Attack key binding. |
| R2.C ShadowMath | `CombatMath` statics ported from SkillCheck/SkillFormula. Shadow calculator that consumes `DefenseBuild` + weapon + target state → predicted `AttackResult`. Conformance tests. |
| R2.D BodyParts | `BodyPartTable` loader from weenies, Quadrant/AttackHeight wiring, damage-location surfacing in HUD. |
| R2.E Plugin | `IAttackEvents` on the plugin API: `OnAttackerNotification`, `OnDefenderNotification`, `OnEvasion`. Plugin can query shadow math for "preview" computations. |

### Non-goals for acdream (server territory)

These are explicitly **server-side** and we do NOT implement them:

- Authoritative damage calculation (we predict, server decides).
- Enchantment arbitration (prots vs vulns vs items).
- Stamina, health, mana updates (server sends `PrivateUpdateVital`).
- Evasion RNG (we just receive evade/hit from server).
- Proc triggering (Blood Drinker etc.).
- NPC AI — attack target selection, aggro lists, faction matrices.

Our job: predict correctly, send correctly, display correctly.

---

## Appendix: Key constants cheat-sheet

| Constant                        | Value              | Source                                  |
|---------------------------------|--------------------|-----------------------------------------|
| `ThrustThreshold`               | 0.33f              | DM Guide p.150, `WorldObject_Weapon.cs:1033` |
| `KickThreshold`                 | 0.75f              | `Player_Melee.cs:432`                   |
| `SkillFormula.DefaultMod`       | 0.011f             | melee/finesse/thrown/atlatl attribute scale |
| `SkillFormula.BowMod`           | 0.008f             | bow/crossbow attribute scale            |
| `SkillFormula.ArmorMod`         | 200f/3f ≈ 66.667   | armor half-life constant                |
| `SkillCheck` physical factor    | 0.03f              | logistic steepness for melee/missile    |
| `SkillCheck` magic factor       | 0.07f              | magic resist steepness                  |
| `defaultPhysicalCritFrequency`  | 0.10f              | 10% base melee/missile crit             |
| `defaultMagicCritFrequency`     | 0.05f              | 5% base magic crit                      |
| `defaultCritDamageMultiplier`   | 1.0f               | crit multiplier added to 1.0 = 2.0× max |
| `ElementalDamageBonusPvPReduction` | 0.5f            | PvP elemental halved                    |
| `MeleeDistance`                 | 0.6f               | direct-attack range                     |
| `StickyDistance`                | 4.0f               | sticky melee range                      |
| `RepeatDistance`                | 16.0f              | auto-repeat cutoff                      |
| `MinAttackSpeed`                | 0.5                | anim speed clamp (lower bound)          |
| `MaxAttackSpeed`                | 2.0                | anim speed clamp (upper bound)          |
| Natural Res cap                 | 0.5f (= 50%)       | at strAndEnd 440+                       |
| PKLogoffTimer                   | 2 minutes          | logout freeze                           |
| Lifestone protection window     | 5 minutes          | after LS tie                            |
| Stamina mod cap                 | 0.5f (50% less)    | at Endurance ~290                       |
| NoStaminaUse evasion cap        | 0.75f (75%)        | at Endurance ~290                       |

**Remember:** every one of these is tunable by ACE's `PropertyManager` but
the defaults above match what retail clients observed across live pcaps.
Use these as the acdream defaults and expose them via the plugin API for
debugging / emulator compatibility.
