using Theriapolis.Core.Data; using Theriapolis.Core.Entities; using Theriapolis.Core.Rules.Character; using Theriapolis.Core.World; namespace Theriapolis.Core.Rules.Reputation; /// /// Phase 6 M5 — faction-driven NPC allegiance flips. Per the plan §4.6: /// /// Patrol aggression: a friendly/neutral NPC with a faction id flips /// their to /// when the player's local standing with that faction crosses the /// threshold (≤ -51). /// /// Sticky once Hostile: the flip doesn't bounce back if standing /// recovers mid-tick — only on chunk re-stream (NPC despawns + reloads /// fresh from template). This avoids flickering allegiance between /// frames and matches CRPG convention ("you killed a brigand who saw /// you stab a guard last week — they remember"). /// public static class FactionAggression { /// /// Walk every faction-affiliated NPC. Flip non-hostile ones to /// Hostile when the player's local standing with their faction /// crosses the HOSTILE threshold. Returns the number of NPCs flipped /// this tick. /// /// Patrol-aggro reads faction standing directly rather than through /// the disposition lens — a constable doesn't care about your clade /// or your personal history with them; they care that their faction /// says you're wanted. /// public static int UpdateAllegiances( ActorManager actors, Rules.Character.Character pc, PlayerReputation rep, ContentResolver content, WorldState world, ulong worldSeed) { if (pc is null) return 0; int flipped = 0; foreach (var npc in actors.Npcs) { if (!npc.IsAlive) continue; if (npc.Allegiance == Allegiance.Hostile) continue; if (npc.Allegiance == Allegiance.Player) continue; // Phase 6.5 M7 — sticky betrayal aggro fires unconditionally, // independent of faction id (it could be a betrayed lone wolf). if (npc.PermanentAggroAfterBetrayal) { npc.Allegiance = Allegiance.Hostile; flipped++; continue; } if (string.IsNullOrEmpty(npc.FactionId)) continue; int factionStanding = ResolveFactionStanding(npc, rep, content, world, worldSeed); if (factionStanding <= C.REP_HOSTILE_THRESHOLD) { npc.Allegiance = Allegiance.Hostile; flipped++; } } return flipped; } /// /// Local faction standing as perceived by this NPC's home settlement /// (post-propagation), or the global standing if no home is set. /// private static int ResolveFactionStanding( NpcActor npc, PlayerReputation rep, ContentResolver content, WorldState world, ulong worldSeed) { if (npc.HomeSettlementId is { } hid) { foreach (var s in world.Settlements) if (s.Id == hid) return RepPropagation.LocalStandingFor(npc.FactionId, s, worldSeed, rep.Ledger, content.Factions); } return rep.Factions.Get(npc.FactionId); } }