Combat

Threat / Aggro

Server-only accumulated threat tables ("who do I hate most"): first-seen bonus, exact exponential decay, forget threshold, TargetChanged hooks. Feeding (damage/taunts) and consuming (Targeting selector, Agents brains) are composition bridges — Crossplay:Threat:DriveTargeting flips Targeting to highest-threat-first. Zero wire; inert until fed.

Server
services.AddCrossplayThreat();
Depends on
world
Shape
server-only · zero wire

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

IThreatService

Server-side threat API — accumulated aggro tables, one per holder entity. The piece stores and ranks NUMBERS; what generates threat (damage, heals, taunts, proximity) and what consumes it (a Targeting selector, an Agents behavior, a boss script) are composition bridges or game glue. Works for MMO aggro, zombie hordes, tower-defense taunts — any "who do I hate most" question.

  • void Add(long holderId, long sourceId, float amount)

    Adds threat from sourceId onto holderId's table (the first entry of a source also gains the configured first-seen bonus). Negative amounts reduce; an entry falling to/below the forget threshold is dropped.

  • void Clear(long holderId)

    Forgets a holder's whole table (a leash reset, a wipe, an evade).

  • void Drop(long holderId, long sourceId)

    Forgets one source on one holder's table.

  • float GetThreat(long holderId, long sourceId)

    The threat sourceId currently holds on holderId's table (0 when absent).

  • event Action<long, long> TargetChanged

    Raised when a holder's highest-threat source CHANGES: (holder id, new highest source id — 0 when the table emptied). The hook brains/selectors use to switch targets.

  • bool TryGetHighest(long holderId, out long sourceId)

    The holder's current highest-threat source, if it holds any.

Configuration 1

Every tunable lives in an options object — there are no magic numbers to hunt for.

ThreatOptions

Configurable threat parameters (defaults here; never inlined in logic).

  • float DecayIntervalSeconds { get; set; }

    Seconds between decay sweeps (decay is exact over elapsed time regardless of the sweep cadence; this only bounds sweep frequency).

  • float DecayPerSecond { get; set; }

    Fraction of every entry that decays away per second (0.1 = 10%/s). 0 (the default) = threat never decays.

  • bool DriveTargeting { get; set; }

    When true (via the Hosting composition flag Crossplay:Threat:DriveTargeting), the Targeting piece's selector seam is bridged to highest-threat-first. The piece itself never reads this — it documents the composition switch.

  • float FirstSeenBonus { get; set; }

    Extra threat granted the FIRST time a source enters a holder's table — the "first tag" stickiness that stops a fresh attacker from instantly stealing aggro. 0 (the default) = no bonus.

  • float ForgetBelow { get; set; }

    Entries that decay below this are forgotten entirely.

  • int MaxEntriesPerHolder { get; set; }

    Upper bound on one holder's table. When full, a new source replaces the lowest entry only if it enters with MORE threat — the table never grows past this.