refactor: one master reference behind ControlMaster and SummonMaster (#2670)
## Summary `BaseCreature` stored the pet owner (`ControlMaster`) and the summoner (`SummonMaster`) in two fields. Every flow that manages pets kept them in step by hand: summon, transfer, stable, claim, ball of summoning and GM obey. They now share one `_master` field, and the two properties read it through the Controlled and Summoned flags: ```csharp ControlMaster => Controlled ? master : null SummonMaster => Controlled && !Summoned ? null : master ``` Every existing reader keeps its meaning: - `ControlMaster` is still "the owner". - `SummonMaster` is still "the summoner, controlled or not". Blade spirits, energy vortexes, clones, undead minions and enraged creatures (a summon master without `Summoned`) never become visible through `ControlMaster`, so the ungated readers (ML notoriety, aggressor entries and similar) behave as before. - Setting `SummonMaster` on a plain pet does nothing, instead of quietly replacing the owner. - `SetControlMaster` now keeps both masters in step, so the hand-written `SummonMaster` copies are removed from ball of summoning, GM obey, pet transfer, auto-stable and claim, the animal trainer's stable and claim, and jail stabling. ## Serialization BaseCreature moves to v24 with one serialized master (slot 15); the old `SummonMaster` slot is dropped. - `MigrateFrom(V23Content)` resolves absent fields through the same default-value methods the live reader uses. - A save that carried both masters (a summon traded before ownership followed transfers) resolves to the owner. Legacy pre-codegen streams follow the same rule. Requires `ModernUO.Serialization.Generator` 4.2.0 (#2671). `UOContent.Tests` was still on 4.1.0 and is bumped to match. ## Test plan - [x] `MasterShapes_RoundTrip`: controlled pet, controlled summon, energy vortex, enraged creature - [x] `SummonMaster_OnPlainPet_KeepsTheOwner`: owner and follower counts unchanged - [x] `LegacyV22Stream_DifferingMasters_OwnerWins` - [x] `V23Stream_MigratesToOneMaster_AndDefaultsAbsentFields`: both master slots -> owner; flags past bit 32; absent fields take class defaults - [x] UOContent.Tests (1168 passed, 2 skipped) and Server.Tests (907) green
This commit is contained in:
parent
77bd0d9a64
commit
05b6ca9d5d
10 changed files with 723 additions and 133 deletions
|
|
@ -182,22 +182,50 @@ public class BaseCreatureSerializationTests : IDisposable
|
|||
Assert.Equal(master, copy.LastOwner);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UncontrolledSummon_KeepsItsSummonMaster()
|
||||
// One serialized master; the Controlled and Summoned flags decide which views expose it.
|
||||
[Theory]
|
||||
[InlineData(true, false)] // controlled pet
|
||||
[InlineData(true, true)] // controlled summon
|
||||
[InlineData(false, true)] // energy vortex: summoned, never controlled
|
||||
[InlineData(false, false)] // EnragedCreature: a summon master without Summoned
|
||||
public void MasterShapes_RoundTrip(bool controlled, bool summoned)
|
||||
{
|
||||
var bc = NewCreature();
|
||||
var master = NewMaster();
|
||||
|
||||
// Energy vortex-style: summoned with a master, never controlled.
|
||||
bc.Summoned = true;
|
||||
bc.SummonMaster = master;
|
||||
bc.Summoned = summoned;
|
||||
|
||||
if (controlled)
|
||||
{
|
||||
bc.SetControlMaster(master);
|
||||
}
|
||||
else
|
||||
{
|
||||
bc.SummonMaster = master;
|
||||
}
|
||||
|
||||
var copy = Load(Snapshot(bc));
|
||||
|
||||
Assert.True(copy.Summoned);
|
||||
Assert.False(copy.Controlled);
|
||||
Assert.Equal(master, copy.SummonMaster);
|
||||
Assert.Null(copy.ControlMaster);
|
||||
Assert.Equal(controlled, copy.Controlled);
|
||||
Assert.Equal(summoned, copy.Summoned);
|
||||
Assert.Equal(controlled ? master : null, copy.ControlMaster);
|
||||
Assert.Equal(controlled && !summoned ? null : master, copy.SummonMaster);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SummonMaster_OnPlainPet_KeepsTheOwner()
|
||||
{
|
||||
var bc = NewCreature();
|
||||
var owner = NewMaster();
|
||||
var other = NewMaster();
|
||||
|
||||
bc.SetControlMaster(owner);
|
||||
bc.SummonMaster = other;
|
||||
|
||||
Assert.Equal(owner, bc.ControlMaster);
|
||||
Assert.Null(bc.SummonMaster);
|
||||
Assert.Equal(bc.ControlSlots, owner.Followers);
|
||||
Assert.Equal(0, other.Followers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -418,8 +446,8 @@ public class BaseCreatureSerializationTests : IDisposable
|
|||
Assert.Equal(0.6, copy.CurrentMoveSpeed); // passive mode, inheriting
|
||||
}
|
||||
|
||||
// ControlMaster and SummonMaster are independent references: a summon master can
|
||||
// exist without Summoned (EnragedCreature), and a controlled summon carries both.
|
||||
// A summon master can exist without Summoned (EnragedCreature), and a controlled summon
|
||||
// reports the same mobile as both.
|
||||
[Theory]
|
||||
[InlineData(true, true, false, false)] // controlled pet
|
||||
[InlineData(true, true, true, true)] // controlled summon, SummonEnd on the wire
|
||||
|
|
@ -452,4 +480,79 @@ public class BaseCreatureSerializationTests : IDisposable
|
|||
Assert.InRange(copy.SummonEndValue, summonEnd - TimeSpan.FromSeconds(1), summonEnd + TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
// Before summon ownership followed transfers, a traded summon kept its caster as summon master.
|
||||
[Fact]
|
||||
public void LegacyV22Stream_DifferingMasters_OwnerWins()
|
||||
{
|
||||
var owner = NewMaster();
|
||||
var caster = NewMaster();
|
||||
|
||||
var copy = LoadLegacyV22(true, owner, true, caster, Core.Now + TimeSpan.FromMinutes(5));
|
||||
|
||||
Assert.Equal(owner, copy.ControlMaster);
|
||||
Assert.Equal(owner, copy.SummonMaster);
|
||||
}
|
||||
|
||||
// v23 bit positions: every save-flagged field in schema order (DefaultAI is not flagged).
|
||||
private const int V23Controlled = 13;
|
||||
private const int V23ControlMaster = 14;
|
||||
private const int V23Summoned = 20;
|
||||
private const int V23SummonEnd = 21;
|
||||
private const int V23SummonMaster = 22;
|
||||
private const int V23RemoveStep = 50;
|
||||
private const int V23CorpseNameOverride = 52;
|
||||
|
||||
// v23 wrote the owner and the summoner to separate slots; v24 keeps one master.
|
||||
[Fact]
|
||||
public void V23Stream_MigratesToOneMaster_AndDefaultsAbsentFields()
|
||||
{
|
||||
var owner = NewMaster();
|
||||
var caster = NewMaster();
|
||||
var summonEnd = Core.Now + TimeSpan.FromMinutes(5);
|
||||
|
||||
var donor = new MobileStub();
|
||||
donor.DefaultMobileInit();
|
||||
_created.Add(donor);
|
||||
|
||||
var writer = new BufferWriter(true);
|
||||
donor.Serialize(writer);
|
||||
|
||||
writer.Write(23); // version
|
||||
writer.Write(
|
||||
1UL << V23Controlled | 1UL << V23ControlMaster | 1UL << V23Summoned | 1UL << V23SummonEnd |
|
||||
1UL << V23SummonMaster | 1UL << V23RemoveStep | 1UL << V23CorpseNameOverride
|
||||
);
|
||||
writer.WriteEncodedInt((int)AIType.AI_Melee); // DefaultAI
|
||||
writer.Write(owner); // ControlMaster
|
||||
writer.WriteAnchoredTime(summonEnd); // SummonEnd
|
||||
writer.Write(caster); // SummonMaster
|
||||
writer.WriteEncodedInt(3); // RemoveStep
|
||||
writer.Write("a migrated corpse"); // CorpseNameOverride
|
||||
|
||||
var buffer = new byte[writer.Position];
|
||||
writer.Buffer.AsSpan(0, (int)writer.Position).CopyTo(buffer);
|
||||
|
||||
var copy = Load(buffer);
|
||||
|
||||
Assert.True(copy.Controlled);
|
||||
Assert.True(copy.Summoned);
|
||||
Assert.Equal(owner, copy.ControlMaster);
|
||||
Assert.Equal(owner, copy.SummonMaster);
|
||||
Assert.InRange(copy.SummonEndValue, summonEnd - TimeSpan.FromSeconds(1), summonEnd + TimeSpan.FromSeconds(1));
|
||||
Assert.Equal(3, copy.RemoveStep);
|
||||
Assert.Equal("a migrated corpse", copy.CorpseNameOverride);
|
||||
|
||||
// Absent fields take the class defaults, not default(T).
|
||||
Assert.Equal(AIType.AI_Melee, copy.AI);
|
||||
Assert.Equal(-1, copy.HitsMaxSeed);
|
||||
Assert.Equal(1, copy.ControlSlots);
|
||||
Assert.Equal(BaseCreature.MaxLoyalty, copy.Loyalty);
|
||||
Assert.Equal(100, copy.PhysicalDamage);
|
||||
Assert.Equal(10, copy.RangeHome);
|
||||
Assert.Equal(FightMode.Closest, copy.FightMode);
|
||||
Assert.Equal(0.3, copy.ActiveSpeed);
|
||||
Assert.Equal(0.6, copy.PassiveSpeed);
|
||||
Assert.NotNull(copy.Owners);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit.SkippableFact" Version="1.5.85" />
|
||||
<PackageReference Include="ModernUO.Serialization.Generator" Version="4.1.0" PrivateAssets="all" />
|
||||
<PackageReference Include="ModernUO.Serialization.Generator" Version="4.2.0" PrivateAssets="all" />
|
||||
<ProjectReference Include="..\Server\Server.csproj" />
|
||||
<ProjectReference Include="..\UOContent\UOContent.csproj" />
|
||||
<ProjectReference Include="..\Server.Tests\Server.Tests.csproj" />
|
||||
|
|
|
|||
|
|
@ -228,11 +228,6 @@ public partial class BallOfSummoning : Item, TranslocationItem
|
|||
{
|
||||
pet.SetControlMaster(from);
|
||||
|
||||
if (pet.SummonMaster != null)
|
||||
{
|
||||
pet.SummonMaster = from;
|
||||
}
|
||||
|
||||
pet.ControlTarget = from;
|
||||
pet.ControlOrder = OrderType.Follow;
|
||||
|
||||
|
|
|
|||
465
Projects/UOContent/Migrations/Server.Mobiles.BaseCreature.v24.json
generated
Normal file
465
Projects/UOContent/Migrations/Server.Mobiles.BaseCreature.v24.json
generated
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
{
|
||||
"version": 24,
|
||||
"type": "Server.Mobiles.BaseCreature",
|
||||
"properties": [
|
||||
{
|
||||
"name": "DefaultAI",
|
||||
"type": "Server.Mobiles.AIType",
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "CurrentAI",
|
||||
"type": "Server.Mobiles.AIType",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "RangePerception",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RangeFight",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RangeHome",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Team",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FightMode",
|
||||
"type": "Server.Mobiles.FightMode",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "ActiveSpeed",
|
||||
"type": "double",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PassiveSpeed",
|
||||
"type": "double",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CurrentSpeed",
|
||||
"type": "double",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ActiveMoveSpeed",
|
||||
"type": "double",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PassiveMoveSpeed",
|
||||
"type": "double",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Home",
|
||||
"type": "Server.Point3D",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveUOTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Point3D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "HomeMap",
|
||||
"type": "Server.Map",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveUOTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Map"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Controlled",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Master",
|
||||
"type": "Server.Mobile",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "SerializableInterfaceMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "ControlTarget",
|
||||
"type": "Server.Mobile",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "SerializableInterfaceMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "ControlDest",
|
||||
"type": "Server.Point3D",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveUOTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Point3D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ControlOrder",
|
||||
"type": "Server.Mobiles.OrderType",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "MinTameSkill",
|
||||
"type": "double",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Tamable",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Summoned",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SummonEnd",
|
||||
"type": "System.DateTime",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"AnchoredTime"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ControlSlots",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Loyalty",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CurrentWayPoint",
|
||||
"type": "Server.Items.WayPoint",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "SerializableInterfaceMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "HitsMaxSeed",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "StamMaxSeed",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ManaMaxSeed",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DamageMin",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DamageMax",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PhysicalResistanceSeed",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FireResistSeed",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ColdResistSeed",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PoisonResistSeed",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EnergyResistSeed",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PhysicalDamage",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FireDamage",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ColdDamage",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PoisonDamage",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EnergyDamage",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Owners",
|
||||
"type": "System.Collections.Generic.List\u003CServer.Mobile\u003E",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "ListMigrationRule",
|
||||
"ruleArguments": [
|
||||
"@Tidy",
|
||||
"Server.Mobile",
|
||||
"SerializableInterfaceMigrationRule"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IsDeadPet",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IsBonded",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "BondingBegin",
|
||||
"type": "System.DateTime",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "OwnerAbandonTime",
|
||||
"type": "System.DateTime",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "HasGeneratedLoot",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IsParagon",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Friends",
|
||||
"type": "System.Collections.Generic.List\u003CServer.Mobile\u003E",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "ListMigrationRule",
|
||||
"ruleArguments": [
|
||||
"@Tidy",
|
||||
"Server.Mobile",
|
||||
"SerializableInterfaceMigrationRule"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RemoveIfUntamed",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RemoveStep",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PendingDeleteTimer",
|
||||
"type": "Server.Timer",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "TimerMigrationRule",
|
||||
"ruleArguments": [
|
||||
"@AnchoredTimer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CorpseNameOverride",
|
||||
"type": "string",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -451,11 +451,6 @@ public abstract partial class BaseAI
|
|||
|
||||
Mobile.SetControlMaster(e.Mobile);
|
||||
|
||||
if (Mobile.SummonMaster != null)
|
||||
{
|
||||
Mobile.SummonMaster = e.Mobile;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,11 +156,6 @@ internal sealed partial class TransferItem : Item
|
|||
|
||||
private void TransferPetOwnership(Mobile from, Mobile to)
|
||||
{
|
||||
if (_creature.SummonMaster != null)
|
||||
{
|
||||
_creature.SummonMaster = to;
|
||||
}
|
||||
|
||||
_creature.ClearPetFriends();
|
||||
_creature.IssueOrder(OrderType.Follow, null, to);
|
||||
_creature.BondingBegin = DateTime.MinValue;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ModernUO.CodeGeneratedEvents;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Collections;
|
||||
using Server.ContextMenus;
|
||||
|
|
@ -135,7 +134,7 @@ namespace Server.Mobiles
|
|||
public int CompareTo(DamageStore ds) => (ds?.m_Damage ?? 0).CompareTo(m_Damage);
|
||||
}
|
||||
|
||||
[SerializationGenerator(23, false)]
|
||||
[SerializationGenerator(24, false)]
|
||||
public abstract partial class BaseCreature : Mobile, IHonorTarget, IQuestGiver
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(BaseCreature));
|
||||
|
|
@ -446,10 +445,12 @@ namespace Server.Mobiles
|
|||
InvalidateProperties();
|
||||
}
|
||||
|
||||
// Follower bookkeeping brackets the assignment, so the property is hand-written.
|
||||
private Mobile _controlMaster;
|
||||
// ControlMaster and SummonMaster are views of this one reference, gated by Controlled and Summoned.
|
||||
[SerializableField(15, getter: "private", setter: "private")]
|
||||
[SaveFlag(nameof(ShouldSerializeMaster))]
|
||||
private Mobile _master;
|
||||
|
||||
private bool ShouldSerializeControlMaster() => _controlMaster != null;
|
||||
private bool ShouldSerializeMaster() => _master != null;
|
||||
|
||||
[SerializableField(16)]
|
||||
[SaveFlag(nameof(ShouldSerializeControlTarget))]
|
||||
|
|
@ -503,13 +504,8 @@ namespace Server.Mobiles
|
|||
|
||||
private bool ShouldSerializeSummonEnd() => _summoned;
|
||||
|
||||
// Follower bookkeeping brackets the assignment, so the property is hand-written.
|
||||
private Mobile _summonMaster;
|
||||
|
||||
private bool ShouldSerializeSummonMaster() => _summonMaster != null;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(24)]
|
||||
[SerializableField(23)]
|
||||
[SaveFlag(nameof(ShouldSerializeControlSlots), nameof(ControlSlotsDefaultValue))]
|
||||
[SerializedCommandProperty(AccessLevel.Administrator)]
|
||||
private int _controlSlots = 1;
|
||||
|
|
@ -519,7 +515,7 @@ namespace Server.Mobiles
|
|||
private int ControlSlotsDefaultValue() => 1;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(25, allowFieldChange: nameof(ClampLoyalty))]
|
||||
[SerializableField(24, allowFieldChange: nameof(ClampLoyalty))]
|
||||
[SaveFlag(nameof(ShouldSerializeLoyalty), nameof(LoyaltyDefaultValue))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _loyalty;
|
||||
|
|
@ -534,7 +530,7 @@ namespace Server.Mobiles
|
|||
return true;
|
||||
}
|
||||
|
||||
[SerializableField(26)]
|
||||
[SerializableField(25)]
|
||||
[SaveFlag(nameof(ShouldSerializeCurrentWayPoint))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private WayPoint _currentWayPoint;
|
||||
|
|
@ -542,7 +538,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializeCurrentWayPoint() => _currentWayPoint != null;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(27)]
|
||||
[SerializableField(26)]
|
||||
[SaveFlag(nameof(ShouldSerializeHitsMaxSeed), nameof(HitsMaxSeedDefaultValue))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _hitsMaxSeed = -1;
|
||||
|
|
@ -552,7 +548,7 @@ namespace Server.Mobiles
|
|||
private int HitsMaxSeedDefaultValue() => -1;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(28)]
|
||||
[SerializableField(27)]
|
||||
[SaveFlag(nameof(ShouldSerializeStamMaxSeed), nameof(StamMaxSeedDefaultValue))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _stamMaxSeed = -1;
|
||||
|
|
@ -562,7 +558,7 @@ namespace Server.Mobiles
|
|||
private int StamMaxSeedDefaultValue() => -1;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(29)]
|
||||
[SerializableField(28)]
|
||||
[SaveFlag(nameof(ShouldSerializeManaMaxSeed), nameof(ManaMaxSeedDefaultValue))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _manaMaxSeed = -1;
|
||||
|
|
@ -572,7 +568,7 @@ namespace Server.Mobiles
|
|||
private int ManaMaxSeedDefaultValue() => -1;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(30)]
|
||||
[SerializableField(29)]
|
||||
[SaveFlag(nameof(ShouldSerializeDamageMin), nameof(DamageMinDefaultValue))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _damageMin = -1;
|
||||
|
|
@ -582,7 +578,7 @@ namespace Server.Mobiles
|
|||
private int DamageMinDefaultValue() => -1;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(31)]
|
||||
[SerializableField(30)]
|
||||
[SaveFlag(nameof(ShouldSerializeDamageMax), nameof(DamageMaxDefaultValue))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _damageMax = -1;
|
||||
|
|
@ -592,7 +588,7 @@ namespace Server.Mobiles
|
|||
private int DamageMaxDefaultValue() => -1;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(32, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SerializableField(31, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SaveFlag(nameof(ShouldSerializePhysicalResistanceSeed))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _physicalResistanceSeed;
|
||||
|
|
@ -602,7 +598,7 @@ namespace Server.Mobiles
|
|||
private void OnResistanceSeedChange(int oldValue, int newValue) => UpdateResistances();
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(33, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SerializableField(32, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SaveFlag(nameof(ShouldSerializeFireResistSeed))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _fireResistSeed;
|
||||
|
|
@ -610,7 +606,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializeFireResistSeed() => _fireResistSeed != 0;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(34, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SerializableField(33, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SaveFlag(nameof(ShouldSerializeColdResistSeed))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _coldResistSeed;
|
||||
|
|
@ -618,7 +614,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializeColdResistSeed() => _coldResistSeed != 0;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(35, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SerializableField(34, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SaveFlag(nameof(ShouldSerializePoisonResistSeed))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _poisonResistSeed;
|
||||
|
|
@ -626,7 +622,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializePoisonResistSeed() => _poisonResistSeed != 0;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(36, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SerializableField(35, fieldChanged: nameof(OnResistanceSeedChange))]
|
||||
[SaveFlag(nameof(ShouldSerializeEnergyResistSeed))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _energyResistSeed;
|
||||
|
|
@ -634,7 +630,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializeEnergyResistSeed() => _energyResistSeed != 0;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(37)]
|
||||
[SerializableField(36)]
|
||||
[SaveFlag(nameof(ShouldSerializePhysicalDamage), nameof(PhysicalDamageDefaultValue))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _physicalDamage = 100;
|
||||
|
|
@ -644,7 +640,7 @@ namespace Server.Mobiles
|
|||
private int PhysicalDamageDefaultValue() => 100;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(38)]
|
||||
[SerializableField(37)]
|
||||
[SaveFlag(nameof(ShouldSerializeFireDamage))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _fireDamage;
|
||||
|
|
@ -652,7 +648,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializeFireDamage() => _fireDamage != 0;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(39)]
|
||||
[SerializableField(38)]
|
||||
[SaveFlag(nameof(ShouldSerializeColdDamage))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _coldDamage;
|
||||
|
|
@ -660,7 +656,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializeColdDamage() => _coldDamage != 0;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(40)]
|
||||
[SerializableField(39)]
|
||||
[SaveFlag(nameof(ShouldSerializePoisonDamage))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _poisonDamage;
|
||||
|
|
@ -668,7 +664,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializePoisonDamage() => _poisonDamage != 0;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(41)]
|
||||
[SerializableField(40)]
|
||||
[SaveFlag(nameof(ShouldSerializeEnergyDamage))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _energyDamage;
|
||||
|
|
@ -676,7 +672,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializeEnergyDamage() => _energyDamage != 0;
|
||||
|
||||
[Tidy]
|
||||
[SerializableField(42, setter: "private")]
|
||||
[SerializableField(41, setter: "private")]
|
||||
[SaveFlag(nameof(ShouldSerializeOwners), nameof(OwnersDefaultValue))]
|
||||
private List<Mobile> _owners;
|
||||
|
||||
|
|
@ -688,13 +684,13 @@ namespace Server.Mobiles
|
|||
|
||||
private List<Mobile> OwnersDefaultValue() => new();
|
||||
|
||||
[SerializableField(43)]
|
||||
[SerializableField(42)]
|
||||
[SaveFlag(nameof(ShouldSerializeIsDeadPet))]
|
||||
private bool _isDeadPet;
|
||||
|
||||
private bool ShouldSerializeIsDeadPet() => _isDeadPet;
|
||||
|
||||
[SerializableField(44, fieldChanged: nameof(OnBondedChange))]
|
||||
[SerializableField(43, fieldChanged: nameof(OnBondedChange))]
|
||||
[SaveFlag(nameof(ShouldSerializeIsBonded))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private bool _isBonded;
|
||||
|
|
@ -703,21 +699,21 @@ namespace Server.Mobiles
|
|||
|
||||
private void OnBondedChange(bool oldValue, bool newValue) => InvalidateProperties();
|
||||
|
||||
[SerializableField(45)]
|
||||
[SerializableField(44)]
|
||||
[SaveFlag(nameof(ShouldSerializeBondingBegin))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private DateTime _bondingBegin;
|
||||
|
||||
private bool ShouldSerializeBondingBegin() => _bondingBegin != DateTime.MinValue;
|
||||
|
||||
[SerializableField(46)]
|
||||
[SerializableField(45)]
|
||||
[SaveFlag(nameof(ShouldSerializeOwnerAbandonTime))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private DateTime _ownerAbandonTime;
|
||||
|
||||
private bool ShouldSerializeOwnerAbandonTime() => _ownerAbandonTime != DateTime.MinValue;
|
||||
|
||||
[SerializableField(47)]
|
||||
[SerializableField(46)]
|
||||
[SaveFlag(nameof(ShouldSerializeHasGeneratedLoot))]
|
||||
private bool _hasGeneratedLoot;
|
||||
|
||||
|
|
@ -729,7 +725,7 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializeIsParagon() => _isParagon;
|
||||
|
||||
[Tidy]
|
||||
[SerializableField(49, setter: "private")]
|
||||
[SerializableField(48, setter: "private")]
|
||||
[SaveFlag(nameof(ShouldSerializeFriends))]
|
||||
private List<Mobile> _friends;
|
||||
|
||||
|
|
@ -739,7 +735,7 @@ namespace Server.Mobiles
|
|||
return _friends?.Count > 0;
|
||||
}
|
||||
|
||||
[SerializableField(50)]
|
||||
[SerializableField(49)]
|
||||
[SaveFlag(nameof(ShouldSerializeRemoveIfUntamed))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private bool _removeIfUntamed;
|
||||
|
|
@ -747,21 +743,21 @@ namespace Server.Mobiles
|
|||
private bool ShouldSerializeRemoveIfUntamed() => _removeIfUntamed;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(51)]
|
||||
[SerializableField(50)]
|
||||
[SaveFlag(nameof(ShouldSerializeRemoveStep))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _removeStep;
|
||||
|
||||
private bool ShouldSerializeRemoveStep() => _removeStep != 0;
|
||||
|
||||
[SerializableField(52, setter: "private")]
|
||||
[SerializableField(51, setter: "private")]
|
||||
[SaveFlag(nameof(ShouldSerializePendingDeleteTimer))]
|
||||
[DeserializeTimer(nameof(DeserializePendingDeleteTimer))]
|
||||
private Timer _pendingDeleteTimer;
|
||||
|
||||
// Stabled and controlled pets never resume a delete countdown.
|
||||
private bool ShouldSerializePendingDeleteTimer() =>
|
||||
_pendingDeleteTimer?.Running == true && !IsStabled && !(_controlled && _controlMaster != null);
|
||||
_pendingDeleteTimer?.Running == true && !IsStabled && ControlMaster == null;
|
||||
|
||||
private void DeserializePendingDeleteTimer(TimeSpan delay)
|
||||
{
|
||||
|
|
@ -769,7 +765,7 @@ namespace Server.Mobiles
|
|||
_pendingDeleteTimer.Start();
|
||||
}
|
||||
|
||||
[SerializableField(53)]
|
||||
[SerializableField(52)]
|
||||
[SaveFlag(nameof(ShouldSerializeCorpseNameOverride))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private string _corpseNameOverride;
|
||||
|
|
@ -847,7 +843,6 @@ namespace Server.Mobiles
|
|||
Debug = false;
|
||||
|
||||
_controlled = false;
|
||||
_controlMaster = null;
|
||||
ControlTarget = null;
|
||||
_controlOrder = OrderType.None;
|
||||
|
||||
|
|
@ -931,7 +926,7 @@ namespace Server.Mobiles
|
|||
|
||||
public virtual double WeaponAbilityChance => 0.4;
|
||||
|
||||
[SerializableProperty(48, useField: nameof(_isParagon))]
|
||||
[SerializableProperty(47, useField: nameof(_isParagon))]
|
||||
[SaveFlag(nameof(ShouldSerializeIsParagon))]
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public bool IsParagon
|
||||
|
|
@ -1028,8 +1023,8 @@ namespace Server.Mobiles
|
|||
}
|
||||
|
||||
public virtual bool IsNecroFamiliar =>
|
||||
Summoned && _controlMaster != null &&
|
||||
SummonFamiliarSpell.Table.TryGetValue(_controlMaster, out var bc) && bc == this;
|
||||
Summoned && ControlMaster != null &&
|
||||
SummonFamiliarSpell.Table.TryGetValue(ControlMaster, out var bc) && bc == this;
|
||||
|
||||
public virtual bool DeleteCorpseOnDeath => !Core.AOS && _summoned;
|
||||
|
||||
|
|
@ -1157,54 +1152,53 @@ namespace Server.Mobiles
|
|||
}
|
||||
}
|
||||
|
||||
[SerializableProperty(15, useField: nameof(_controlMaster))]
|
||||
[SaveFlag(nameof(ShouldSerializeControlMaster))]
|
||||
// Assigned before Controlled is raised (SetControlMaster), so the setter cannot be gated.
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public Mobile ControlMaster
|
||||
{
|
||||
get => _controlMaster;
|
||||
get => _controlled ? _master : null;
|
||||
set
|
||||
{
|
||||
if (_controlMaster == value || this == value)
|
||||
if (ControlMaster == value || this == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveFollowers();
|
||||
_controlMaster = value;
|
||||
AddFollowers();
|
||||
if (_controlMaster != null)
|
||||
SetMaster(value);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
StopDeleteTimer();
|
||||
}
|
||||
|
||||
Delta(MobileDelta.Noto);
|
||||
this.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
[SerializableProperty(23, useField: nameof(_summonMaster))]
|
||||
[SaveFlag(nameof(ShouldSerializeSummonMaster))]
|
||||
// The summoner, controlled or not. Enraged creatures have one without being Summoned.
|
||||
// A plain pet has no summoner, so assigning one would overwrite its owner.
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public Mobile SummonMaster
|
||||
{
|
||||
get => _summonMaster;
|
||||
get => _controlled && !_summoned ? null : _master;
|
||||
set
|
||||
{
|
||||
if (_summonMaster == value || this == value)
|
||||
if (SummonMaster == value || this == value || _controlled && !_summoned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveFollowers();
|
||||
_summonMaster = value;
|
||||
AddFollowers();
|
||||
|
||||
Delta(MobileDelta.Noto);
|
||||
this.MarkDirty();
|
||||
SetMaster(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetMaster(Mobile value)
|
||||
{
|
||||
RemoveFollowers();
|
||||
Master = value;
|
||||
AddFollowers();
|
||||
|
||||
Delta(MobileDelta.Noto);
|
||||
}
|
||||
|
||||
// Fires on every assignment, not only changes: a reissued order is a command (retarget, re-anchor).
|
||||
// A raw assignment is system-issued; player commands go through IssueOrder.
|
||||
[SerializableProperty(18, useField: nameof(_controlOrder))]
|
||||
|
|
@ -1271,7 +1265,7 @@ namespace Server.Mobiles
|
|||
}
|
||||
|
||||
InvalidateProperties();
|
||||
_controlMaster?.InvalidateProperties();
|
||||
ControlMaster?.InvalidateProperties();
|
||||
this.MarkDirty();
|
||||
}
|
||||
|
||||
|
|
@ -2195,9 +2189,75 @@ namespace Server.Mobiles
|
|||
}
|
||||
}
|
||||
|
||||
// Absent fields resolve through the same default-value methods the live reader uses.
|
||||
private void MigrateFrom(V23Content content)
|
||||
{
|
||||
_defaultAI = content.DefaultAI;
|
||||
_currentAI = content.CurrentAI ?? CurrentAIDefaultValue();
|
||||
_rangePerception = content.RangePerception ?? RangePerceptionDefaultValue();
|
||||
_rangeFight = content.RangeFight ?? RangeFightDefaultValue();
|
||||
_rangeHome = content.RangeHome ?? RangeHomeDefaultValue();
|
||||
_team = content.Team ?? 0;
|
||||
_fightMode = content.FightMode ?? FightModeDefaultValue();
|
||||
_activeSpeed = content.ActiveSpeed ?? ActiveSpeedDefaultValue();
|
||||
_passiveSpeed = content.PassiveSpeed ?? PassiveSpeedDefaultValue();
|
||||
_currentSpeed = content.CurrentSpeed ?? CurrentSpeedDefaultValue();
|
||||
_activeMoveSpeed = content.ActiveMoveSpeed ?? ActiveMoveSpeedDefaultValue();
|
||||
_passiveMoveSpeed = content.PassiveMoveSpeed ?? PassiveMoveSpeedDefaultValue();
|
||||
_home = content.Home ?? Point3D.Zero;
|
||||
_homeMap = content.HomeMap;
|
||||
_controlled = content.Controlled;
|
||||
// A traded summon could still carry its caster as summon master; the owner wins.
|
||||
_master = content.ControlMaster ?? content.SummonMaster;
|
||||
_controlTarget = content.ControlTarget;
|
||||
_controlDest = content.ControlDest ?? Point3D.Zero;
|
||||
_controlOrder = content.ControlOrder ?? OrderType.None;
|
||||
_minTameSkill = content.MinTameSkill ?? 0;
|
||||
_tamable = content.Tamable;
|
||||
_summoned = content.Summoned;
|
||||
_summonEnd = content.SummonEnd ?? DateTime.MinValue;
|
||||
_controlSlots = content.ControlSlots ?? ControlSlotsDefaultValue();
|
||||
_loyalty = content.Loyalty ?? LoyaltyDefaultValue();
|
||||
_currentWayPoint = content.CurrentWayPoint;
|
||||
_hitsMaxSeed = content.HitsMaxSeed ?? HitsMaxSeedDefaultValue();
|
||||
_stamMaxSeed = content.StamMaxSeed ?? StamMaxSeedDefaultValue();
|
||||
_manaMaxSeed = content.ManaMaxSeed ?? ManaMaxSeedDefaultValue();
|
||||
_damageMin = content.DamageMin ?? DamageMinDefaultValue();
|
||||
_damageMax = content.DamageMax ?? DamageMaxDefaultValue();
|
||||
_physicalResistanceSeed = content.PhysicalResistanceSeed ?? 0;
|
||||
_fireResistSeed = content.FireResistSeed ?? 0;
|
||||
_coldResistSeed = content.ColdResistSeed ?? 0;
|
||||
_poisonResistSeed = content.PoisonResistSeed ?? 0;
|
||||
_energyResistSeed = content.EnergyResistSeed ?? 0;
|
||||
_physicalDamage = content.PhysicalDamage ?? PhysicalDamageDefaultValue();
|
||||
_fireDamage = content.FireDamage ?? 0;
|
||||
_coldDamage = content.ColdDamage ?? 0;
|
||||
_poisonDamage = content.PoisonDamage ?? 0;
|
||||
_energyDamage = content.EnergyDamage ?? 0;
|
||||
_owners = content.Owners ?? OwnersDefaultValue();
|
||||
_isDeadPet = content.IsDeadPet;
|
||||
_isBonded = content.IsBonded;
|
||||
_bondingBegin = content.BondingBegin ?? DateTime.MinValue;
|
||||
_ownerAbandonTime = content.OwnerAbandonTime ?? DateTime.MinValue;
|
||||
_hasGeneratedLoot = content.HasGeneratedLoot;
|
||||
_isParagon = content.IsParagon;
|
||||
_friends = content.Friends;
|
||||
_removeIfUntamed = content.RemoveIfUntamed;
|
||||
_removeStep = content.RemoveStep ?? 0;
|
||||
|
||||
if (content.PendingDeleteTimerDelay != TimeSpan.MinValue)
|
||||
{
|
||||
DeserializePendingDeleteTimer(content.PendingDeleteTimerDelay);
|
||||
}
|
||||
|
||||
_corpseNameOverride = content.CorpseNameOverride;
|
||||
}
|
||||
|
||||
// Pre-codegen loads only (versions 0-22); post-codegen bumps use MigrateFrom.
|
||||
private void Deserialize(IGenericReader reader, int version)
|
||||
{
|
||||
Mobile controlMaster = null;
|
||||
|
||||
_currentAI = (AIType)reader.ReadInt();
|
||||
_defaultAI = (AIType)reader.ReadInt();
|
||||
|
||||
|
|
@ -2245,7 +2305,7 @@ namespace Server.Mobiles
|
|||
_fightMode = (FightMode)reader.ReadInt();
|
||||
|
||||
_controlled = reader.ReadBool();
|
||||
_controlMaster = reader.ReadEntity<Mobile>();
|
||||
controlMaster = reader.ReadEntity<Mobile>();
|
||||
_controlTarget = reader.ReadEntity<Mobile>();
|
||||
_controlDest = reader.ReadPoint3D();
|
||||
_controlOrder = (OrderType)reader.ReadInt();
|
||||
|
|
@ -2273,7 +2333,6 @@ namespace Server.Mobiles
|
|||
_fightMode = FightMode.Closest;
|
||||
|
||||
_controlled = false;
|
||||
_controlMaster = null;
|
||||
_controlTarget = null;
|
||||
_controlOrder = OrderType.None;
|
||||
}
|
||||
|
|
@ -2294,9 +2353,12 @@ namespace Server.Mobiles
|
|||
|
||||
if (version >= 5)
|
||||
{
|
||||
_summonMaster = reader.ReadEntity<Mobile>();
|
||||
_master = reader.ReadEntity<Mobile>();
|
||||
}
|
||||
|
||||
// A transferred summon kept its caster as summon master; the owner wins.
|
||||
_master = controlMaster ?? _master;
|
||||
|
||||
if (version >= 6)
|
||||
{
|
||||
_hitsMaxSeed = reader.ReadInt();
|
||||
|
|
@ -2457,7 +2519,7 @@ namespace Server.Mobiles
|
|||
|
||||
if (IsAnimatedDead)
|
||||
{
|
||||
AnimateDeadSpell.Register(_summonMaster, this);
|
||||
AnimateDeadSpell.Register(SummonMaster, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2566,7 +2628,7 @@ namespace Server.Mobiles
|
|||
|
||||
public void RemoveFollowers()
|
||||
{
|
||||
var master = _controlMaster ?? _summonMaster;
|
||||
var master = _master;
|
||||
if (master != null)
|
||||
{
|
||||
master.Followers -= Math.Min(ControlSlots, master.Followers);
|
||||
|
|
@ -2580,7 +2642,7 @@ namespace Server.Mobiles
|
|||
|
||||
public void AddFollowers()
|
||||
{
|
||||
var master = _controlMaster ?? _summonMaster;
|
||||
var master = _master;
|
||||
if (master != null)
|
||||
{
|
||||
master.Followers += ControlSlots;
|
||||
|
|
@ -2661,7 +2723,7 @@ namespace Server.Mobiles
|
|||
|
||||
if (IsAnimatedDead)
|
||||
{
|
||||
AnimateDeadSpell.Unregister(_summonMaster, this);
|
||||
AnimateDeadSpell.Unregister(SummonMaster, this);
|
||||
}
|
||||
|
||||
if (Summoned && SummonMaster != null)
|
||||
|
|
@ -2842,7 +2904,7 @@ namespace Server.Mobiles
|
|||
}
|
||||
|
||||
public override bool IsHarmfulCriminal(Mobile target) =>
|
||||
(!Controlled || target != _controlMaster) && (!Summoned || target != _summonMaster) &&
|
||||
target != ControlMaster && (!Summoned || target != SummonMaster) &&
|
||||
(target is not BaseCreature { InitialInnocent: true } creature || creature.Controlled) &&
|
||||
(target is not PlayerMobile mobile || mobile.PermaFlags.Count <= 0) && base.IsHarmfulCriminal(target);
|
||||
|
||||
|
|
@ -2850,16 +2912,9 @@ namespace Server.Mobiles
|
|||
{
|
||||
base.CriminalAction(message);
|
||||
|
||||
if (Controlled || Summoned)
|
||||
if ((Controlled || Summoned) && _master?.Player == true)
|
||||
{
|
||||
if (_controlMaster?.Player == true)
|
||||
{
|
||||
_controlMaster.CriminalAction(false);
|
||||
}
|
||||
else if (_summonMaster?.Player == true)
|
||||
{
|
||||
_summonMaster.CriminalAction(false);
|
||||
}
|
||||
_master.CriminalAction(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2867,7 +2922,7 @@ namespace Server.Mobiles
|
|||
{
|
||||
base.DoHarmful(target, indirect);
|
||||
|
||||
if (target == this || target == _controlMaster || target == _summonMaster || !Controlled && !Summoned)
|
||||
if (target == this || target == _master || !Controlled && !Summoned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -3644,7 +3699,7 @@ namespace Server.Mobiles
|
|||
{
|
||||
CreatureEvents.CreatureDeletedEvent(this);
|
||||
|
||||
var m = _controlMaster;
|
||||
var m = ControlMaster;
|
||||
SetControlMaster(null);
|
||||
|
||||
SummonMaster = null;
|
||||
|
|
@ -3951,14 +4006,9 @@ namespace Server.Mobiles
|
|||
return BardMaster;
|
||||
}
|
||||
|
||||
if (_controlled && _controlMaster != null)
|
||||
if ((_controlled || _summoned) && _master != null)
|
||||
{
|
||||
return _controlMaster;
|
||||
}
|
||||
|
||||
if (_summoned && _summonMaster != null)
|
||||
{
|
||||
return _summonMaster;
|
||||
return _master;
|
||||
}
|
||||
|
||||
return base.GetDamageMaster(damagee);
|
||||
|
|
@ -4549,7 +4599,7 @@ namespace Server.Mobiles
|
|||
|
||||
if (IsBondable && !IsBonded)
|
||||
{
|
||||
var master = _controlMaster;
|
||||
var master = ControlMaster;
|
||||
|
||||
if (master != null && master == from) // So friends can't start the bonding process
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3553,7 +3553,6 @@ namespace Server.Mobiles
|
|||
pet.Internalize();
|
||||
|
||||
pet.SetControlMaster(null);
|
||||
pet.SummonMaster = null;
|
||||
|
||||
pet.IsStabled = true;
|
||||
pet.StabledBy = this;
|
||||
|
|
@ -3602,11 +3601,6 @@ namespace Server.Mobiles
|
|||
{
|
||||
pet.SetControlMaster(this);
|
||||
|
||||
if (pet.SummonMaster != null)
|
||||
{
|
||||
pet.SummonMaster = this;
|
||||
}
|
||||
|
||||
pet.ControlTarget = this;
|
||||
pet.ControlOrder = OrderType.Follow;
|
||||
|
||||
|
|
|
|||
|
|
@ -255,7 +255,6 @@ namespace Server.Mobiles
|
|||
pet.Internalize();
|
||||
|
||||
pet.SetControlMaster(null);
|
||||
pet.SummonMaster = null;
|
||||
|
||||
pet.IsStabled = true;
|
||||
pet.StabledBy = from;
|
||||
|
|
@ -357,11 +356,6 @@ namespace Server.Mobiles
|
|||
{
|
||||
pet.SetControlMaster(from);
|
||||
|
||||
if (pet.SummonMaster != null)
|
||||
{
|
||||
pet.SummonMaster = from;
|
||||
}
|
||||
|
||||
pet.ControlTarget = from;
|
||||
pet.ControlOrder = OrderType.Follow;
|
||||
|
||||
|
|
|
|||
|
|
@ -144,7 +144,6 @@ public class JailSystem : GenericPersistence
|
|||
bc.Internalize();
|
||||
|
||||
bc.SetControlMaster(null);
|
||||
bc.SummonMaster = null;
|
||||
|
||||
bc.IsStabled = true;
|
||||
bc.StabledBy = from;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue