fix: Preserve corpse notoriety across server restart (#2426)

BaseCreatures are deleted on death (Mobile.OnDeath calls Delete for non-players), so after save/restart the corpse's _owner reference resolves to null. CorpseNotoriety gated its entire creature branch on `target.Owner is BaseCreature`, falling through to player-corpse logic once the reference vanished. That made monster corpses turn red (body.IsMonster -> Murderer) and innocent NPC corpses turn grey (null is not PlayerMobile -> CanBeAttacked) on the next restart.

Snapshots the relevant owner state into CorpseFlag at corpse creation: OwnerWasBaseCreature, OwnerWasSummoned, OwnerWasAnimatedDead. Folds the standalone _murderer bool into CorpseFlag.Murderer for consistency with Criminal. CorpseNotoriety now consults the flags so the creature branch stays correct without a live mobile reference.

Bumps Corpse serialization to v16 with a MigrateFrom(V15Content) that maps the old Murderer bool onto the new flag. Pre-fix corpses already on disk decay within 7 minutes; their first post-restart color may be wrong, which is acceptable.

Also documents that the schema generator must be run after every version bump (`dotnet tool run ModernUOSchemaGenerator -- ModernUO.slnx`) since `dotnet build` does not emit migration JSON files.
This commit is contained in:
Kamron Batman 2026-05-03 02:15:01 -07:00 • committed by GitHub
parent a74c7f9d4e
commit 29e4ecdb1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 251 additions and 29 deletions

View file

@ -297,22 +297,25 @@ namespace Server.Misc
}
}
if (target.Owner is BaseCreature creature)
// BaseCreatures are deleted on death, so target.Owner is null after a server restart.
// The OwnerWasBaseCreature flag is the persisted snapshot that survives the live mobile.
var creature = target.Owner as BaseCreature;
if (target.OwnerWasBaseCreature || creature != null)
{
if (srcFaction != null && trgFaction != null && srcFaction != trgFaction && source.Map == Faction.Facet)
{
return Notoriety.Enemy;
}
if (CheckHouseFlag(source, creature, target.Location, target.Map))
if (creature != null && CheckHouseFlag(source, creature, target.Location, target.Map))
{
return Notoriety.CanBeAttacked;
}
var actual = Notoriety.CanBeAttacked;
if (target.Murderer || body.IsMonster && IsSummoned(creature) ||
creature.IsAnimatedDead)
if (target.Murderer || body.IsMonster && target.OwnerWasSummoned || target.OwnerWasAnimatedDead)
{
actual = Notoriety.Murderer;
}