mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
Adv 2.0 D&D Phase R7: post-completion audit hardening
High-priority fixes from the multi-agent audit of Adventure 2.0: - Phase 11 nil-deref cluster: zoneOrFallback() helper replaces 8 unsafe getZone(_) sites in dnd_zone_cmd.go. Corrupted zone IDs render a placeholder instead of panicking. - Briefing/recap idempotency: deliverBriefing/deliverRecap now claim the rollover via a conditional UPDATE. Double-fires from clock skew or restart become no-ops; supply burn and day++ no longer reapply. - Graceful ticker shutdown: AdventurePlugin gains stopCh + Stop(); all 11 background tickers now select on stopCh in addition to ticker.C. - Mood decay (§3.2): math.Round(elapsed*2) replaces int() truncation so sub-hour gaps decay correctly. - 24h auto-abandon (§4.3): getActiveZoneRun returns clean slate and abandons stale runs whose LastActionAt is over 24h old. - Respec / auto-migrate orphan cleanup: !respec and the auto-migrated draft wipe now abandon active zone runs and expeditions before deleting the dnd_character row. - Phase R combat-link: applyBossDefeatThreat now wired from resolveBossRoom (-20 threat); applyRoomCombatThreatForUser adds +5/+8 from non-boss/elite kills (§8.1). - Starvation → forced extraction: briefing-time check forces extract with §10.2 coin tax when supplies hit zero. - GMNat20/Nat1 narration wired into resolveCombatRoom and resolveBossRoom (nat-20 takes precedence over nat-1). - Treasure-undo race: LoadAndDelete on both timer-fire and `undo` paths so only one side wins. - Battle Master: Disarming, Menacing, Parry maneuvers added (3 → 6 of 10). Remaining 4 (Pushing, Goading, Riposte, Commander's Strike) documented inline as needing ally/reaction mechanics the engine doesn't model. - Threat-70 warning: tracked in RegionState["siege_warning_fired"] so a drop-and-recross doesn't re-fire the beat. - region_state JSON decode error now logged via slog.Warn instead of silently discarded. Failing TestProdDB_DnDLayer fixed via option (a): track migrated characters and only run round-trip / idempotency assertions on those, skipping pre-existing prod-DB rows accumulated from live bot use. New tests in dnd_audit_phase_R7_test.go cover: 24h auto-abandon, briefing double-fire idempotency, threat-70 warning idempotency, multi-region extract→resume state preservation, and starvation forced-extraction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package plugin
|
||||
|
||||
import "math"
|
||||
|
||||
// Phase 10 SUB2a — subclass combat hooks.
|
||||
//
|
||||
// applySubclassPassives layers subclass-driven flags onto CombatModifiers
|
||||
@@ -429,6 +431,46 @@ func init() {
|
||||
mods.HealItem += base + abilityModifier(c.CHA)
|
||||
},
|
||||
}
|
||||
// Three more maneuvers using existing CombatModifiers fields. The
|
||||
// remaining four spec maneuvers (Pushing, Goading, Riposte, Commander's
|
||||
// Strike) need ally-targeting / reaction mechanics that the one-shot
|
||||
// engine doesn't model — they're omitted rather than approximated
|
||||
// inaccurately.
|
||||
dndActiveAbilities["disarming_attack"] = DnDAbility{
|
||||
ID: "disarming_attack",
|
||||
Name: "Disarming Attack",
|
||||
Class: ClassFighter,
|
||||
Subclass: SubclassBattleMaster,
|
||||
Resource: "superiority",
|
||||
Description: "Knock the enemy's weapon aside: their damage is reduced for the rest of the fight (consumes 1 superiority die).",
|
||||
Apply: func(c *DnDCharacter, mods *CombatModifiers) {
|
||||
// 25% incoming-damage cut models a fighter who is now
|
||||
// swinging an improvised weapon at -d4-ish.
|
||||
mods.DamageReduct = math.Max(mods.DamageReduct, 0.25)
|
||||
},
|
||||
}
|
||||
dndActiveAbilities["menacing_attack"] = DnDAbility{
|
||||
ID: "menacing_attack",
|
||||
Name: "Menacing Attack",
|
||||
Class: ClassFighter,
|
||||
Subclass: SubclassBattleMaster,
|
||||
Resource: "superiority",
|
||||
Description: "Snarl as you swing — the enemy hesitates and skips its first attack (consumes 1 superiority die).",
|
||||
Apply: func(c *DnDCharacter, mods *CombatModifiers) {
|
||||
mods.SpellEnemySkipFirst = true
|
||||
},
|
||||
}
|
||||
dndActiveAbilities["parry"] = DnDAbility{
|
||||
ID: "parry",
|
||||
Name: "Parry",
|
||||
Class: ClassFighter,
|
||||
Subclass: SubclassBattleMaster,
|
||||
Resource: "superiority",
|
||||
Description: "Set yourself for incoming blows — physical damage taken is sharply reduced (consumes 1 superiority die).",
|
||||
Apply: func(c *DnDCharacter, mods *CombatModifiers) {
|
||||
mods.DamageReduct = math.Max(mods.DamageReduct, 0.40)
|
||||
},
|
||||
}
|
||||
|
||||
// Phase 10 SUB2c — Cleric Channel Divinity. All three Cleric subclasses
|
||||
// share the "channel_divinity" resource pool (2/long-rest in our model;
|
||||
|
||||
Reference in New Issue
Block a user