fix: Fixes dirty-tracking gaps in generated content: setters, sub-object owners, BaseVendor (#2609)
## Why Delta world saves re-serialize an entity only when it has been marked dirty. An audit of the generated classes found three ways serialized state changes without a mark; this PR closes the ones that do not need a new generator package. ## What - **Custom `[SerializableProperty]` setters mark dirty.** Twelve hand-written setters assigned their backing field without `this.MarkDirty()`. `PlagueBeastLord.OpenedBy` was an auto-property carrying the attribute with no backing field at all; it is now a generated field with the same name, order and command-property exposure. - **Generated sub-objects are linked to their owner.** Without a `[DirtyTrackingEntity]` member the generator emits setters that mark nothing. Eight entity-owned sub-objects now carry the link and receive the owner through the constructor the generator calls: `BOBFilter` (owner is `IEntity`: a `PlayerMobile` or a `BulkOrderBook`), `BOBLargeSubEntry`, `PuzzleChestSolution` and `PuzzleChestSolutionAndTime`, `TalismanAttribute` (the random factories now take the talisman), `VendorItem`, `PlayerBBMessage`, `RaffleEntry`, `ShardPollOption`. Migration schemas were regenerated with the pinned tool; the only change is the value rule argument becoming `DeserializationRequiresParent`. - **BaseVendor uses the generator; restock amounts are no longer persisted.** The only state it wrote was which buy entries had grown restock amounts, packed by index into the live `SBInfos` tables. Restock is transient now and rebuilds on load; version 1 records are read and discarded through the legacy path. Every vendor subclass now serializes through a generated chain. ## Generator 4.1.0 This PR adopts SerializationGenerator 4.1.0 (modernuo/SerializationGenerator#55): SG3019/SG3020 diagnostics, `[VolatileSerializedState]`, `StopXxx()` timer helpers, and owner-constructor preference for sub-objects (so dictionary values are constructed with their owner; the 4.0.0 relink fallback is gone). `PlayerVendor.v3.json` gains `DeserializationRequiresParent` for its `VendorItem` values so the migration content struct constructs them with their vendor. SG3019 is an error under `TreatWarningsAsErrors` and generator diagnostics ignore pragmas, so the five contexts that live in whole-file player-keyed persistences (`ChampionTitle`, `ChampionTitleContext`, `MurderContext`, `VirtueContext`, `JailRecord`) now carry a `[DirtyTrackingEntity]` link to their `PlayerMobile`, which is where they will live once those blobs move onto the player record. `JailSystem.EmptyRecord` keeps a null player (`[CanBeNull]`). `ChampionTitle` needs both a context and a player constructor because the generator resolves the rule against the containing type but emits the call with the parent field; a comment in the file records this. ## Wire format Unchanged. No version bumps except `BaseVendor` 1 to 2 (which now writes nothing of its own). Schema diffs are rule-argument only (`DeserializationRequiresParent`). ## Testing Server.Tests 848 passed, UOContent.Tests 759 passed.
This commit is contained in:
parent
a2c232f4a8
commit
84153fba58
49 changed files with 223 additions and 214 deletions
|
|
@ -3,7 +3,7 @@
|
|||
"isRoot": true,
|
||||
"tools": {
|
||||
"modernuoschemagenerator": {
|
||||
"version": "4.0.0",
|
||||
"version": "4.1.0",
|
||||
"commands": [
|
||||
"ModernUOSchemaGenerator"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@
|
|||
<PackageReference Include="LibDeflate.Bindings" Version="1.0.4" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.11" />
|
||||
|
||||
<PackageReference Include="ModernUO.Serialization.Annotations" Version="4.0.0" />
|
||||
<PackageReference Include="ModernUO.Serialization.Generator" Version="4.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="ModernUO.Serialization.Annotations" Version="4.1.0" />
|
||||
<PackageReference Include="ModernUO.Serialization.Generator" Version="4.1.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Migrations/*.v*.json" />
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ namespace Server.Engines.BulkOrders;
|
|||
[SerializationGenerator(2)]
|
||||
public partial class BOBFilter
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private IEntity _owner;
|
||||
|
||||
public BOBFilter(IEntity owner) => _owner = owner;
|
||||
|
||||
[SerializableField(0)]
|
||||
[SaveFlag(nameof(ShouldSerializeType))]
|
||||
private int _type;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public partial class BOBLargeEntry : BaseBOBEntry
|
|||
|
||||
for (var i = 0; i < _entries.Length; ++i)
|
||||
{
|
||||
_entries[i] = new BOBLargeSubEntry(bod.Entries[i]);
|
||||
_entries[i] = new BOBLargeSubEntry(this, bod.Entries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public partial class BOBLargeEntry : BaseBOBEntry
|
|||
|
||||
for (var i = 0; i < Entries.Length; ++i)
|
||||
{
|
||||
_entries[i] = new BOBLargeSubEntry();
|
||||
_entries[i] = new BOBLargeSubEntry(this);
|
||||
_entries[i].Deserialize(reader);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ namespace Server.Engines.BulkOrders;
|
|||
[SerializationGenerator(0)]
|
||||
public partial class BOBLargeSubEntry
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private BOBLargeEntry _parent;
|
||||
|
||||
[SerializableField(0, setter: "private")]
|
||||
private Type _itemType;
|
||||
|
||||
|
|
@ -21,12 +24,11 @@ public partial class BOBLargeSubEntry
|
|||
[SerializableField(3, setter: "private")]
|
||||
private int _graphic;
|
||||
|
||||
public BOBLargeSubEntry()
|
||||
{
|
||||
}
|
||||
public BOBLargeSubEntry(BOBLargeEntry parent) => _parent = parent;
|
||||
|
||||
public BOBLargeSubEntry(LargeBulkEntry lbe)
|
||||
public BOBLargeSubEntry(BOBLargeEntry parent, LargeBulkEntry lbe)
|
||||
{
|
||||
_parent = parent;
|
||||
_itemType = lbe.Details.Type;
|
||||
_amountCur = lbe.Amount;
|
||||
_number = lbe.Details.Number;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public partial class BulkOrderBook : Item, ISecurable
|
|||
LootType = LootType.Blessed;
|
||||
|
||||
_entries = [];
|
||||
_filter = new BOBFilter();
|
||||
_filter = new BOBFilter(this);
|
||||
|
||||
_level = SecureLevel.CoOwners;
|
||||
}
|
||||
|
|
@ -224,7 +224,7 @@ public partial class BulkOrderBook : Item, ISecurable
|
|||
|
||||
_bookName = reader.ReadString();
|
||||
|
||||
_filter = new BOBFilter();
|
||||
_filter = new BOBFilter(this);
|
||||
_filter.Deserialize(reader);
|
||||
|
||||
var count = reader.ReadEncodedInt();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,23 @@
|
|||
using System;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Engines.CannedEvil;
|
||||
|
||||
[SerializationGenerator(0)]
|
||||
public partial class ChampionTitle
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private PlayerMobile _player;
|
||||
|
||||
public ChampionTitle(ChampionTitleContext context) : this(context?.Player)
|
||||
{
|
||||
}
|
||||
|
||||
// The generator resolves the deserialization constructor against the owning type, but emits the
|
||||
// owner's own dirty-tracking reference (the player) at the call site, so both overloads exist.
|
||||
public ChampionTitle(PlayerMobile player) => _player = player;
|
||||
|
||||
[EncodedInt]
|
||||
[SerializableField(0)]
|
||||
private int _value;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ public partial class ChampionTitleContext
|
|||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _harrower;
|
||||
|
||||
[DirtyTrackingEntity]
|
||||
private PlayerMobile _player;
|
||||
|
||||
public PlayerMobile Player => _player;
|
||||
|
|
@ -45,7 +46,7 @@ public partial class ChampionTitleContext
|
|||
throw new NotImplementedException($"Cannot find ChampionSpawnType value {type}.");
|
||||
}
|
||||
|
||||
title = new ChampionTitle();
|
||||
title = new ChampionTitle(this);
|
||||
title.Deserialize(reader);
|
||||
}
|
||||
}
|
||||
|
|
@ -290,7 +291,7 @@ public partial class ChampionTitleContext
|
|||
return null;
|
||||
}
|
||||
|
||||
return title ??= new ChampionTitle();
|
||||
return title ??= new ChampionTitle(this);
|
||||
}
|
||||
|
||||
public void SetValue(ChampionSpawnType type, int value)
|
||||
|
|
@ -313,7 +314,7 @@ public partial class ChampionTitleContext
|
|||
}
|
||||
else
|
||||
{
|
||||
title = new ChampionTitle();
|
||||
title = new ChampionTitle(this);
|
||||
}
|
||||
|
||||
title.Value = value;
|
||||
|
|
|
|||
|
|
@ -23,11 +23,19 @@ namespace Server.Items
|
|||
[SerializationGenerator(1)]
|
||||
public partial class PuzzleChestSolution
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private PuzzleChest _chest;
|
||||
|
||||
[SerializableField(0)]
|
||||
private PuzzleChestCylinder[] _cylinders;
|
||||
|
||||
public const int Length = 5;
|
||||
|
||||
// Declared first: the generator picks the first matching constructor, and a deserialized
|
||||
// solution must know its chest to mark it dirty.
|
||||
public PuzzleChestSolution(PuzzleChest chest) : this() => _chest = chest;
|
||||
|
||||
// Transient solutions (player guesses being edited in a gump) have no owning chest.
|
||||
public PuzzleChestSolution() =>
|
||||
_cylinders = [RandomCylinder(), RandomCylinder(), RandomCylinder(), RandomCylinder(), RandomCylinder()];
|
||||
|
||||
|
|
@ -42,6 +50,9 @@ namespace Server.Items
|
|||
solution.Cylinders.AsSpan().CopyTo(Cylinders);
|
||||
}
|
||||
|
||||
protected PuzzleChestSolution(PuzzleChest chest, PuzzleChestSolution solution) : this(solution) =>
|
||||
_chest = chest;
|
||||
|
||||
private void Deserialize(IGenericReader reader, int version)
|
||||
{
|
||||
var length = reader.ReadEncodedInt();
|
||||
|
|
@ -174,10 +185,11 @@ namespace Server.Items
|
|||
[SerializableField(0)]
|
||||
private DateTime _when;
|
||||
|
||||
public PuzzleChestSolutionAndTime(DateTime when, PuzzleChestSolution solution) : base(solution) => _when = when;
|
||||
public PuzzleChestSolutionAndTime(PuzzleChest chest, DateTime when, PuzzleChestSolution solution)
|
||||
: base(chest, solution) => _when = when;
|
||||
|
||||
// For serialization
|
||||
public PuzzleChestSolutionAndTime()
|
||||
// The generator deserializes guesses through this constructor so each one knows its chest.
|
||||
public PuzzleChestSolutionAndTime(PuzzleChest chest) : base(chest)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -214,7 +226,7 @@ namespace Server.Items
|
|||
|
||||
private void Deserialize(IGenericReader reader, int version)
|
||||
{
|
||||
_solution = new PuzzleChestSolution();
|
||||
_solution = new PuzzleChestSolution(this);
|
||||
_solution.Deserialize(reader);
|
||||
|
||||
var length = reader.ReadEncodedInt();
|
||||
|
|
@ -238,7 +250,7 @@ namespace Server.Items
|
|||
for (var i = 0; i < guessCount; i++)
|
||||
{
|
||||
var m = reader.ReadEntity<Mobile>();
|
||||
(_guesses[m] = new PuzzleChestSolutionAndTime()).Deserialize(reader);
|
||||
(_guesses[m] = new PuzzleChestSolutionAndTime(this)).Deserialize(reader);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +341,7 @@ namespace Server.Items
|
|||
}
|
||||
else
|
||||
{
|
||||
(_guesses ??= []).Add(m, new PuzzleChestSolutionAndTime(Core.Now, solution));
|
||||
(_guesses ??= []).Add(m, new PuzzleChestSolutionAndTime(this, Core.Now, solution));
|
||||
StartCleanupTimer();
|
||||
|
||||
m.SendGump(new StatusGump(correctCylinders, correctColors));
|
||||
|
|
@ -525,7 +537,7 @@ namespace Server.Items
|
|||
}
|
||||
}
|
||||
|
||||
Solution = new PuzzleChestSolution();
|
||||
Solution = new PuzzleChestSolution(this);
|
||||
}
|
||||
|
||||
private void StartCleanupTimer()
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ public partial class PlantItem : Item, ISecurable
|
|||
var ratio = PlantSystem != null ? (double)PlantSystem.Hits / PlantSystem.MaxHits : 1.0;
|
||||
|
||||
_plantStatus = value;
|
||||
this.MarkDirty();
|
||||
|
||||
if (_plantStatus >= PlantStatus.DecorativePlant)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ public partial class MurderContext
|
|||
_lastMurderTime = Core.Now;
|
||||
}
|
||||
|
||||
[DirtyTrackingEntity]
|
||||
public PlayerMobile _player;
|
||||
|
||||
public PlayerMobile Player => _player;
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ public abstract partial class BaseSpawner : Item, ISpawner
|
|||
{
|
||||
_walkingRange = value;
|
||||
InvalidateProperties();
|
||||
this.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ namespace Server.Engines.Virtues;
|
|||
[SerializationGenerator(1)]
|
||||
public partial class VirtueContext
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private PlayerMobile _player;
|
||||
|
||||
public PlayerMobile Player => _player;
|
||||
|
||||
public VirtueContext(PlayerMobile player) => _player = player;
|
||||
|
||||
private void MigrateFrom(V0Content content)
|
||||
{
|
||||
// Save-flagged values arrive as nullables; unset flags fall back to the same
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ public class VirtueSystem : GenericPersistence
|
|||
for (var i = 0; i < contextCount; i++)
|
||||
{
|
||||
var player = reader.ReadEntity<PlayerMobile>();
|
||||
var virtues = new VirtueContext();
|
||||
var virtues = new VirtueContext(player);
|
||||
virtues.Deserialize(reader);
|
||||
|
||||
if (player != null && virtues.IsUsed())
|
||||
|
|
@ -122,7 +122,7 @@ public class VirtueSystem : GenericPersistence
|
|||
ref var context = ref CollectionsMarshal.GetValueRefOrAddDefault(_playerVirtues, from, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
context = new VirtueContext();
|
||||
context = new VirtueContext(from);
|
||||
}
|
||||
|
||||
return context;
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ namespace Server.Items
|
|||
{
|
||||
UnscaleDurability();
|
||||
_quality = value;
|
||||
this.MarkDirty();
|
||||
ScaleDurability();
|
||||
}
|
||||
}
|
||||
|
|
@ -213,6 +214,7 @@ namespace Server.Items
|
|||
{
|
||||
UnscaleDurability();
|
||||
_durability = value;
|
||||
this.MarkDirty();
|
||||
ScaleDurability();
|
||||
}
|
||||
}
|
||||
|
|
@ -246,6 +248,7 @@ namespace Server.Items
|
|||
UnscaleDurability();
|
||||
|
||||
_resource = value;
|
||||
this.MarkDirty();
|
||||
|
||||
if (CraftItem.RetainsColor(GetType()))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ public abstract partial class FillableContainer : LockableContainer
|
|||
|
||||
ClearContents();
|
||||
_contentType = value;
|
||||
this.MarkDirty();
|
||||
Respawn();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@ public abstract partial class BaseBeverage : Item, IHasQuantity
|
|||
set
|
||||
{
|
||||
_quantity = Math.Clamp(value, 0, MaxQuantity);
|
||||
this.MarkDirty();
|
||||
|
||||
InvalidateProperties();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public partial class BloodwoodSpirit : BaseTalisman
|
|||
|
||||
Removal = TalismanRemoval.Damage;
|
||||
Blessed = GetRandomBlessed();
|
||||
Protection = GetRandomProtection(false);
|
||||
Protection = GetRandomProtection(this, false);
|
||||
|
||||
SkillBonuses.SetValues(0, SkillName.SpiritSpeak, 10.0);
|
||||
SkillBonuses.SetValues(1, SkillName.Necromancy, 5.0);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public partial class TotemOfVoid : BaseTalisman
|
|||
MaxChargeTime = 1800;
|
||||
|
||||
Blessed = GetRandomBlessed();
|
||||
Protection = GetRandomProtection(false);
|
||||
Protection = GetRandomProtection(this, false);
|
||||
|
||||
Attributes.RegenHits = 2;
|
||||
Attributes.LowerManaCost = 10;
|
||||
|
|
|
|||
|
|
@ -74,13 +74,13 @@ public abstract partial class BasePlayerBB : Item, ISecurable
|
|||
|
||||
if (_greeting != null)
|
||||
{
|
||||
board.Greeting = new PlayerBBMessage(_greeting.Time, _greeting.Poster, _greeting.Message);
|
||||
board.Greeting = new PlayerBBMessage(board, _greeting.Time, _greeting.Poster, _greeting.Message);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _messages.Count; i++)
|
||||
{
|
||||
var message = _messages[i];
|
||||
board.AddToMessages(new PlayerBBMessage(message.Time, message.Poster, message.Message));
|
||||
board.AddToMessages(new PlayerBBMessage(board, message.Time, message.Poster, message.Message));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ public abstract partial class BasePlayerBB : Item, ISecurable
|
|||
|
||||
if (text.Length > 0)
|
||||
{
|
||||
var message = new PlayerBBMessage(Core.Now, from, text);
|
||||
var message = new PlayerBBMessage(board, Core.Now, from, text);
|
||||
|
||||
if (_greeting)
|
||||
{
|
||||
|
|
@ -265,6 +265,9 @@ public abstract partial class BasePlayerBB : Item, ISecurable
|
|||
[SerializationGenerator(0)]
|
||||
public partial class PlayerBBMessage
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private BasePlayerBB _board;
|
||||
|
||||
[SerializableField(0)]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private DateTime _time;
|
||||
|
|
@ -277,12 +280,11 @@ public partial class PlayerBBMessage
|
|||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private string _message;
|
||||
|
||||
public PlayerBBMessage()
|
||||
{
|
||||
}
|
||||
public PlayerBBMessage(BasePlayerBB board) => _board = board;
|
||||
|
||||
public PlayerBBMessage(DateTime time, Mobile poster, string message)
|
||||
public PlayerBBMessage(BasePlayerBB board, DateTime time, Mobile poster, string message)
|
||||
{
|
||||
_board = board;
|
||||
_time = time;
|
||||
_poster = poster;
|
||||
_message = message;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public partial class RecallRune : Item
|
|||
set
|
||||
{
|
||||
_house = value;
|
||||
this.MarkDirty();
|
||||
CalculateHue();
|
||||
InvalidateProperties();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ public abstract partial class BaseInstrument : Item, ICraftable, ISlayer
|
|||
{
|
||||
UnscaleUses();
|
||||
_quality = value;
|
||||
this.MarkDirty();
|
||||
InvalidateProperties();
|
||||
ScaleUses();
|
||||
}
|
||||
|
|
@ -109,6 +110,7 @@ public abstract partial class BaseInstrument : Item, ICraftable, ISlayer
|
|||
set
|
||||
{
|
||||
_usesRemaining = value;
|
||||
this.MarkDirty();
|
||||
InvalidateProperties();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace Server.Items;
|
|||
[SerializationGenerator(0)]
|
||||
public partial class RaffleEntry
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private HouseRaffleStone _stone;
|
||||
|
||||
[SerializableField(0, setter: "private")]
|
||||
private Mobile _from;
|
||||
|
||||
|
|
@ -25,15 +28,17 @@ public partial class RaffleEntry
|
|||
[SerializableField(2, setter: "private")]
|
||||
private DateTime _date;
|
||||
|
||||
public RaffleEntry(Mobile from)
|
||||
public RaffleEntry(HouseRaffleStone stone, Mobile from)
|
||||
{
|
||||
_stone = stone;
|
||||
_from = from;
|
||||
_address = from?.NetState?.Address ?? IPAddress.None;
|
||||
_date = Core.Now;
|
||||
}
|
||||
|
||||
public RaffleEntry()
|
||||
public RaffleEntry(HouseRaffleStone stone)
|
||||
{
|
||||
_stone = stone;
|
||||
_from = null;
|
||||
_address = null;
|
||||
_date = Core.Now;
|
||||
|
|
@ -454,7 +459,7 @@ public partial class HouseRaffleStone : Item
|
|||
if (_ticketPrice == 0 || from.Backpack?.ConsumeTotal(typeof(Gold), _ticketPrice) == true ||
|
||||
Banker.Withdraw(from, _ticketPrice))
|
||||
{
|
||||
AddToEntries(new RaffleEntry(from));
|
||||
AddToEntries(new RaffleEntry(this, from));
|
||||
|
||||
from.SendMessage(MessageHue, "You have successfully entered the plot's raffle.");
|
||||
}
|
||||
|
|
@ -539,7 +544,7 @@ public partial class HouseRaffleStone : Item
|
|||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entry = new RaffleEntry();
|
||||
var entry = new RaffleEntry(this);
|
||||
entry.Deserialize(reader);
|
||||
|
||||
if (entry.From == null)
|
||||
|
|
|
|||
|
|
@ -26,19 +26,19 @@ public partial class BaseTalisman
|
|||
BlessedFor = reader.ReadEntity<Mobile>();
|
||||
}
|
||||
|
||||
_protection = new TalismanAttribute();
|
||||
_protection = new TalismanAttribute(this);
|
||||
if (GetOldSaveFlag(flags, OldSaveFlag.Protection))
|
||||
{
|
||||
_protection.Deserialize(reader);
|
||||
}
|
||||
|
||||
_killer = new TalismanAttribute();
|
||||
_killer = new TalismanAttribute(this);
|
||||
if (GetOldSaveFlag(flags, OldSaveFlag.Killer))
|
||||
{
|
||||
_killer.Deserialize(reader);
|
||||
}
|
||||
|
||||
_summoner = new TalismanAttribute();
|
||||
_summoner = new TalismanAttribute(this);
|
||||
if (GetOldSaveFlag(flags, OldSaveFlag.Summoner))
|
||||
{
|
||||
_summoner.Deserialize(reader);
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ public partial class BaseTalisman : Item, IAosItem
|
|||
|
||||
public bool ShouldSerializeProtection() => !_protection.IsEmpty;
|
||||
|
||||
private TalismanAttribute ProtectionDefaultValue() => new();
|
||||
private TalismanAttribute ProtectionDefaultValue() => new(this);
|
||||
|
||||
[SerializedIgnoreDupe]
|
||||
[InvalidateProperties]
|
||||
|
|
@ -166,7 +166,7 @@ public partial class BaseTalisman : Item, IAosItem
|
|||
|
||||
public bool ShouldSerializeKiller() => !_killer.IsEmpty;
|
||||
|
||||
private TalismanAttribute KillerDefaultValue() => new();
|
||||
private TalismanAttribute KillerDefaultValue() => new(this);
|
||||
|
||||
[SerializedIgnoreDupe]
|
||||
[InvalidateProperties]
|
||||
|
|
@ -177,7 +177,7 @@ public partial class BaseTalisman : Item, IAosItem
|
|||
|
||||
public bool ShouldSerializeSummoner() => !_summoner.IsEmpty;
|
||||
|
||||
private TalismanAttribute SummonerDefaultValue() => new();
|
||||
private TalismanAttribute SummonerDefaultValue() => new(this);
|
||||
|
||||
[InvalidateProperties]
|
||||
[SerializableField(5)]
|
||||
|
|
@ -267,9 +267,9 @@ public partial class BaseTalisman : Item, IAosItem
|
|||
{
|
||||
Layer = Layer.Talisman;
|
||||
|
||||
_protection = new TalismanAttribute();
|
||||
_killer = new TalismanAttribute();
|
||||
_summoner = new TalismanAttribute();
|
||||
_protection = new TalismanAttribute(this);
|
||||
_killer = new TalismanAttribute(this);
|
||||
_summoner = new TalismanAttribute(this);
|
||||
Attributes = new AosAttributes(this);
|
||||
SkillBonuses = new AosSkillBonuses(this);
|
||||
}
|
||||
|
|
@ -319,9 +319,9 @@ public partial class BaseTalisman : Item, IAosItem
|
|||
return;
|
||||
}
|
||||
|
||||
talisman._summoner = new TalismanAttribute(_summoner);
|
||||
talisman._protection = new TalismanAttribute(_protection);
|
||||
talisman._killer = new TalismanAttribute(_killer);
|
||||
talisman._summoner = new TalismanAttribute(talisman, _summoner);
|
||||
talisman._protection = new TalismanAttribute(talisman, _protection);
|
||||
talisman._killer = new TalismanAttribute(talisman, _killer);
|
||||
talisman.Attributes = new AosAttributes(newItem, Attributes);
|
||||
talisman.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses);
|
||||
}
|
||||
|
|
@ -660,17 +660,17 @@ public partial class BaseTalisman : Item, IAosItem
|
|||
|
||||
public virtual void SetSummoner(Type type, TextDefinition name)
|
||||
{
|
||||
_summoner = new TalismanAttribute(type, name);
|
||||
_summoner = new TalismanAttribute(this, type, name);
|
||||
}
|
||||
|
||||
public virtual void SetProtection(Type type, TextDefinition name, int amount)
|
||||
{
|
||||
_protection = new TalismanAttribute(type, name, amount);
|
||||
_protection = new TalismanAttribute(this, type, name, amount);
|
||||
}
|
||||
|
||||
public virtual void SetKiller(Type type, TextDefinition name, int amount)
|
||||
{
|
||||
_killer = new TalismanAttribute(type, name, amount);
|
||||
_killer = new TalismanAttribute(this, type, name, amount);
|
||||
}
|
||||
|
||||
public virtual void StartTimer()
|
||||
|
|
@ -712,18 +712,18 @@ public partial class BaseTalisman : Item, IAosItem
|
|||
|
||||
public static Type GetRandomSummonType() => _summons.RandomElement();
|
||||
|
||||
public static TalismanAttribute GetRandomSummoner()
|
||||
public static TalismanAttribute GetRandomSummoner(BaseTalisman owner)
|
||||
{
|
||||
if (Utility.RandomDouble() < 0.975)
|
||||
{
|
||||
return new TalismanAttribute();
|
||||
return new TalismanAttribute(owner);
|
||||
}
|
||||
|
||||
var num = Utility.Random(_summons.Length);
|
||||
|
||||
return num > 14
|
||||
? new TalismanAttribute(_summons[num], _summonLabels[num], 10)
|
||||
: new TalismanAttribute(_summons[num], _summonLabels[num]);
|
||||
? new TalismanAttribute(owner, _summons[num], _summonLabels[num], 10)
|
||||
: new TalismanAttribute(owner, _summons[num], _summonLabels[num]);
|
||||
}
|
||||
|
||||
public static TalismanRemoval GetRandomRemoval()
|
||||
|
|
@ -736,32 +736,32 @@ public partial class BaseTalisman : Item, IAosItem
|
|||
return TalismanRemoval.None;
|
||||
}
|
||||
|
||||
public static TalismanAttribute GetRandomKiller() => GetRandomKiller(true);
|
||||
public static TalismanAttribute GetRandomKiller(BaseTalisman owner) => GetRandomKiller(owner, true);
|
||||
|
||||
public static TalismanAttribute GetRandomKiller(bool includingNone)
|
||||
public static TalismanAttribute GetRandomKiller(BaseTalisman owner, bool includingNone)
|
||||
{
|
||||
if (includingNone && Utility.RandomBool())
|
||||
{
|
||||
return new TalismanAttribute();
|
||||
return new TalismanAttribute(owner);
|
||||
}
|
||||
|
||||
var num = Utility.Random(_killers.Length);
|
||||
|
||||
return new TalismanAttribute(_killers[num], _killerLabels[num], Utility.RandomMinMax(10, 100));
|
||||
return new TalismanAttribute(owner, _killers[num], _killerLabels[num], Utility.RandomMinMax(10, 100));
|
||||
}
|
||||
|
||||
public static TalismanAttribute GetRandomProtection() => GetRandomProtection(true);
|
||||
public static TalismanAttribute GetRandomProtection(BaseTalisman owner) => GetRandomProtection(owner, true);
|
||||
|
||||
public static TalismanAttribute GetRandomProtection(bool includingNone)
|
||||
public static TalismanAttribute GetRandomProtection(BaseTalisman owner, bool includingNone)
|
||||
{
|
||||
if (includingNone && Utility.RandomBool())
|
||||
{
|
||||
return new TalismanAttribute();
|
||||
return new TalismanAttribute(owner);
|
||||
}
|
||||
|
||||
var num = Utility.Random(_killers.Length);
|
||||
|
||||
return new TalismanAttribute(_killers[num], _killerLabels[num], Utility.RandomMinMax(5, 60));
|
||||
return new TalismanAttribute(owner, _killers[num], _killerLabels[num], Utility.RandomMinMax(5, 60));
|
||||
}
|
||||
|
||||
public static SkillName GetRandomSkill() => _skills.RandomElement();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ public partial class RandomTalisman : BaseTalisman
|
|||
[Constructible]
|
||||
public RandomTalisman() : base(GetRandomItemID())
|
||||
{
|
||||
Summoner = GetRandomSummoner();
|
||||
Summoner = GetRandomSummoner(this);
|
||||
|
||||
if (Summoner.IsEmpty)
|
||||
{
|
||||
|
|
@ -36,8 +36,8 @@ public partial class RandomTalisman : BaseTalisman
|
|||
|
||||
Blessed = GetRandomBlessed();
|
||||
Slayer = GetRandomSlayer();
|
||||
Protection = GetRandomProtection();
|
||||
Killer = GetRandomKiller();
|
||||
Protection = GetRandomProtection(this);
|
||||
Killer = GetRandomKiller(this);
|
||||
Skill = GetRandomSkill();
|
||||
ExceptionalBonus = GetRandomExceptional();
|
||||
SuccessBonus = GetRandomSuccessful();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ namespace Server.Items;
|
|||
[SerializationGenerator(1, false)]
|
||||
public partial class TalismanAttribute
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private BaseTalisman _owner;
|
||||
|
||||
[SerializableField(0)]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private Type _type;
|
||||
|
|
@ -19,12 +22,12 @@ public partial class TalismanAttribute
|
|||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private int _amount;
|
||||
|
||||
public TalismanAttribute() : this(null, null)
|
||||
{
|
||||
}
|
||||
public TalismanAttribute(BaseTalisman owner) => _owner = owner;
|
||||
|
||||
public TalismanAttribute(TalismanAttribute copy)
|
||||
public TalismanAttribute(BaseTalisman owner, TalismanAttribute copy)
|
||||
{
|
||||
_owner = owner;
|
||||
|
||||
if (copy != null)
|
||||
{
|
||||
_type = copy.Type;
|
||||
|
|
@ -33,8 +36,9 @@ public partial class TalismanAttribute
|
|||
}
|
||||
}
|
||||
|
||||
public TalismanAttribute(Type type, TextDefinition name, int amount = 0)
|
||||
public TalismanAttribute(BaseTalisman owner, Type type, TextDefinition name, int amount = 0)
|
||||
{
|
||||
_owner = owner;
|
||||
_type = type;
|
||||
_name = name;
|
||||
_amount = amount;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"ruleArguments": [
|
||||
"Server.Engines.BulkOrders.BOBLargeSubEntry",
|
||||
"RawSerializableMigrationRule",
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
"type": "Server.Engines.BulkOrders.BOBFilter",
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -88,7 +88,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"type": "Server.Items.PlayerBBMessage",
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"",
|
||||
"DeserializationRequiresParent",
|
||||
"@CanBeNull"
|
||||
]
|
||||
},
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
"ruleArguments": [
|
||||
"Server.Items.PlayerBBMessage",
|
||||
"RawSerializableMigrationRule",
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
"ruleArguments": [
|
||||
"Server.Items.RaffleEntry",
|
||||
"RawSerializableMigrationRule",
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"type": "Server.Items.PuzzleChestSolution",
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
"Server.Items.PuzzleChestSolutionAndTime",
|
||||
"RawSerializableMigrationRule",
|
||||
"2",
|
||||
"",
|
||||
"DeserializationRequiresParent",
|
||||
"@CanBeNull"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"ruleArguments": [
|
||||
"Server.Misc.ShardPollOption",
|
||||
"RawSerializableMigrationRule",
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
4
Projects/UOContent/Migrations/Server.Mobiles.BaseVendor.v2.json
generated
Normal file
4
Projects/UOContent/Migrations/Server.Mobiles.BaseVendor.v2.json
generated
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"version": 2,
|
||||
"type": "Server.Mobiles.BaseVendor"
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
"Server.Mobiles.VendorItem",
|
||||
"RawSerializableMigrationRule",
|
||||
"1",
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
"Server.Mobiles.VendorItem",
|
||||
"RawSerializableMigrationRule",
|
||||
"1",
|
||||
""
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -738,7 +738,7 @@ namespace Server
|
|||
{
|
||||
var talisman = new BaseTalisman(BaseTalisman.GetRandomItemID());
|
||||
|
||||
talisman.Summoner = BaseTalisman.GetRandomSummoner();
|
||||
talisman.Summoner = BaseTalisman.GetRandomSummoner(talisman);
|
||||
|
||||
if (talisman.Summoner.IsEmpty)
|
||||
{
|
||||
|
|
@ -758,8 +758,8 @@ namespace Server
|
|||
|
||||
talisman.Blessed = BaseTalisman.GetRandomBlessed();
|
||||
talisman.Slayer = BaseTalisman.GetRandomSlayer();
|
||||
talisman.Protection = BaseTalisman.GetRandomProtection();
|
||||
talisman.Killer = BaseTalisman.GetRandomKiller();
|
||||
talisman.Protection = BaseTalisman.GetRandomProtection(talisman);
|
||||
talisman.Killer = BaseTalisman.GetRandomKiller(talisman);
|
||||
talisman.Skill = BaseTalisman.GetRandomSkill();
|
||||
talisman.ExceptionalBonus = BaseTalisman.GetRandomExceptional();
|
||||
talisman.SuccessBonus = BaseTalisman.GetRandomSuccessful();
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ public partial class ShardPoller : Item
|
|||
|
||||
for (var i = 0; i < _options.Length; ++i)
|
||||
{
|
||||
var option = _options[i] = new ShardPollOption();
|
||||
var option = _options[i] = new ShardPollOption(this);
|
||||
option.Deserialize(reader);
|
||||
}
|
||||
}
|
||||
|
|
@ -212,15 +212,23 @@ public partial class ShardPoller : Item
|
|||
[SerializationGenerator(1, false)]
|
||||
public partial class ShardPollOption
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private ShardPoller _poller;
|
||||
|
||||
private int _lineBreaks = -1;
|
||||
|
||||
[SerializableField(1)]
|
||||
private IPAddress[] _voters;
|
||||
|
||||
public ShardPollOption() => _voters = [];
|
||||
|
||||
public ShardPollOption(string title)
|
||||
public ShardPollOption(ShardPoller poller)
|
||||
{
|
||||
_poller = poller;
|
||||
_voters = [];
|
||||
}
|
||||
|
||||
public ShardPollOption(ShardPoller poller, string title)
|
||||
{
|
||||
_poller = poller;
|
||||
_title = title;
|
||||
_voters = [];
|
||||
}
|
||||
|
|
@ -600,7 +608,7 @@ public partial class ShardPollPrompt : Prompt
|
|||
|
||||
if (_option == null)
|
||||
{
|
||||
_poller.AddOption(new ShardPollOption(text));
|
||||
_poller.AddOption(new ShardPollOption(_poller, text));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,7 +21,11 @@ namespace Server.Mobiles
|
|||
public int DevourGoal
|
||||
{
|
||||
get => IsParagon ? _devourGoal + 25 : _devourGoal;
|
||||
set => _devourGoal = value;
|
||||
set
|
||||
{
|
||||
_devourGoal = value;
|
||||
this.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
[Constructible]
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@ namespace Server.Mobiles
|
|||
VirtualArmor = 50;
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
[SerializableProperty(3)]
|
||||
public Mobile OpenedBy { get; set; }
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
[SerializableField(3)]
|
||||
private Mobile _openedBy;
|
||||
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ namespace Server.Mobiles
|
|||
VisibilityList = new List<Mobile>();
|
||||
PermaFlags = new List<Mobile>();
|
||||
|
||||
BOBFilter = new BOBFilter();
|
||||
BOBFilter = new BOBFilter(this);
|
||||
|
||||
m_GameTime = TimeSpan.Zero;
|
||||
m_GuildRank = RankDefinition.Lowest;
|
||||
|
|
@ -2958,7 +2958,7 @@ namespace Server.Mobiles
|
|||
case 13: // just removed m_PaidInsurance list
|
||||
case 12:
|
||||
{
|
||||
BOBFilter = new BOBFilter();
|
||||
BOBFilter = new BOBFilter(this);
|
||||
BOBFilter.Deserialize(reader);
|
||||
goto case 11;
|
||||
}
|
||||
|
|
@ -3081,7 +3081,7 @@ namespace Server.Mobiles
|
|||
}
|
||||
|
||||
PermaFlags ??= new List<Mobile>();
|
||||
BOBFilter ??= new BOBFilter();
|
||||
BOBFilter ??= new BOBFilter(this);
|
||||
|
||||
// Default to member if going from older version to new version (only time it should be null)
|
||||
m_GuildRank ??= RankDefinition.Member;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Collections;
|
||||
using Server.ContextMenus;
|
||||
using Server.Engines.BulkOrders;
|
||||
|
|
@ -24,7 +25,8 @@ namespace Server.Mobiles
|
|||
ThighBoots
|
||||
}
|
||||
|
||||
public abstract class BaseVendor : BaseCreature, IVendor
|
||||
[SerializationGenerator(2, false)]
|
||||
public abstract partial class BaseVendor : BaseCreature, IVendor
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(BaseVendor));
|
||||
private const int MaxSell = 500;
|
||||
|
|
@ -1297,109 +1299,28 @@ namespace Server.Mobiles
|
|||
Region.GetRegion<GuardedRegion>()?.CheckVendorAccess(this, from) != false ||
|
||||
Region != from.Region && from.Region.GetRegion<GuardedRegion>()?.CheckVendorAccess(this, from) != false;
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
[AfterDeserialization]
|
||||
private void AfterDeserialization()
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(1); // version
|
||||
|
||||
var sbInfos = SBInfos;
|
||||
|
||||
for (var i = 0; i < sbInfos?.Count; ++i)
|
||||
{
|
||||
var sbInfo = sbInfos[i];
|
||||
var buyInfo = sbInfo.BuyInfo;
|
||||
|
||||
for (var j = 0; j < buyInfo?.Count; ++j)
|
||||
{
|
||||
var gbi = buyInfo[j];
|
||||
|
||||
var maxAmount = gbi.MaxAmount;
|
||||
|
||||
var doubled = maxAmount switch
|
||||
{
|
||||
40 => 1,
|
||||
80 => 2,
|
||||
160 => 3,
|
||||
320 => 4,
|
||||
640 => 5,
|
||||
999 => 6,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if (doubled > 0)
|
||||
{
|
||||
writer.WriteEncodedInt(1 + j * sbInfos.Count + i);
|
||||
writer.WriteEncodedInt(doubled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEncodedInt(0);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
|
||||
LoadSBInfo();
|
||||
|
||||
var sbInfos = SBInfos;
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
int index;
|
||||
|
||||
while ((index = reader.ReadEncodedInt()) > 0)
|
||||
{
|
||||
var doubled = reader.ReadEncodedInt();
|
||||
|
||||
if (sbInfos != null)
|
||||
{
|
||||
index -= 1;
|
||||
var sbInfoIndex = index % sbInfos.Count;
|
||||
var buyInfoIndex = index / sbInfos.Count;
|
||||
|
||||
if (sbInfoIndex >= 0 && sbInfoIndex < sbInfos.Count)
|
||||
{
|
||||
var sbInfo = sbInfos[sbInfoIndex];
|
||||
var buyInfo = sbInfo.BuyInfo;
|
||||
|
||||
if (buyInfo != null && buyInfoIndex >= 0 && buyInfoIndex < buyInfo.Count)
|
||||
{
|
||||
var gbi = buyInfo[buyInfoIndex];
|
||||
|
||||
var amount = doubled switch
|
||||
{
|
||||
1 => 40,
|
||||
2 => 80,
|
||||
3 => 160,
|
||||
4 => 320,
|
||||
5 => 640,
|
||||
6 => 999,
|
||||
_ => 20
|
||||
};
|
||||
|
||||
gbi.Amount = gbi.MaxAmount = amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsParagon)
|
||||
{
|
||||
IsParagon = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Version 1 persisted which buy entries had grown restock amounts, packed by index into
|
||||
// the live SBInfos tables. Restock is transient now: it rebuilds from SBInfos on load, so
|
||||
// the pairs are read and discarded.
|
||||
private void Deserialize(IGenericReader reader, int version)
|
||||
{
|
||||
while (reader.ReadEncodedInt() > 0)
|
||||
{
|
||||
reader.ReadEncodedInt();
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddCustomContextEntries(Mobile from, ref PooledRefList<ContextMenuEntry> list)
|
||||
{
|
||||
if (from.Alive && IsActiveVendor)
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ public partial class PlayerVendor : Mobile
|
|||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var item = reader.ReadEntity<Item>();
|
||||
var vi = new VendorItem();
|
||||
var vi = new VendorItem(this);
|
||||
vi.Deserialize(reader);
|
||||
_sellItems[item] = vi;
|
||||
}
|
||||
|
|
@ -447,7 +447,7 @@ public partial class PlayerVendor : Mobile
|
|||
{
|
||||
RemoveVendorItem(item);
|
||||
|
||||
var vi = new VendorItem(item, price, description, created);
|
||||
var vi = new VendorItem(this, item, price, description, created);
|
||||
ReplaceInSellItems(item, vi);
|
||||
|
||||
item.InvalidateProperties();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ namespace Server.Mobiles;
|
|||
[SerializationGenerator(0, false)]
|
||||
public partial class VendorItem
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
private PlayerVendor _vendor;
|
||||
|
||||
[SerializableField(0)]
|
||||
private Item _item;
|
||||
|
||||
|
|
@ -16,12 +19,13 @@ public partial class VendorItem
|
|||
[SerializableField(3)]
|
||||
private DateTime _created;
|
||||
|
||||
public VendorItem()
|
||||
{
|
||||
}
|
||||
// The generator deserializes dictionary values through this constructor so every entry
|
||||
// knows its vendor.
|
||||
public VendorItem(PlayerVendor vendor) => _vendor = vendor;
|
||||
|
||||
public VendorItem(Item item, int price, string description, DateTime created)
|
||||
public VendorItem(PlayerVendor vendor, Item item, int price, string description, DateTime created)
|
||||
{
|
||||
_vendor = vendor;
|
||||
_item = item;
|
||||
_price = price;
|
||||
_description = description ?? "";
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
using System;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Systems.JailSystem;
|
||||
|
||||
[SerializationGenerator(0)]
|
||||
public partial class JailRecord
|
||||
{
|
||||
[DirtyTrackingEntity]
|
||||
[CanBeNull]
|
||||
private PlayerMobile _player;
|
||||
|
||||
public JailRecord(PlayerMobile player) => _player = player;
|
||||
|
||||
[SerializableField(0)]
|
||||
private int _jailCount;
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class JailSystem : GenericPersistence
|
|||
|
||||
// Jail map, change this for custom maps
|
||||
public static readonly Map JailMap = Map.Felucca;
|
||||
private static readonly JailRecord EmptyRecord = new();
|
||||
private static readonly JailRecord EmptyRecord = new(null);
|
||||
|
||||
private static readonly HashSet<PlayerMobile> CurrentlyBeingJailed = [];
|
||||
private static readonly Dictionary<PlayerMobile, JailRecord> PlayerJailRecords = [];
|
||||
|
|
@ -96,7 +96,7 @@ public class JailSystem : GenericPersistence
|
|||
|
||||
if (!PlayerJailRecords.TryGetValue(player, out var record))
|
||||
{
|
||||
PlayerJailRecords[player] = record = new JailRecord();
|
||||
PlayerJailRecords[player] = record = new JailRecord(player);
|
||||
}
|
||||
|
||||
record.JailCount++;
|
||||
|
|
@ -409,7 +409,7 @@ public class JailSystem : GenericPersistence
|
|||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var player = reader.ReadEntity<PlayerMobile>();
|
||||
var record = new JailRecord();
|
||||
var record = new JailRecord(player);
|
||||
record.Deserialize(reader);
|
||||
|
||||
if (player != null)
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@
|
|||
<PackageReference Include="ModernUO.CodeGeneratedEvents.Generator" Version="1.0.3.2" PrivateAssets="all" />
|
||||
<PackageReference Include="ZstdNet" Version="1.5.7" />
|
||||
|
||||
<PackageReference Include="ModernUO.Serialization.Annotations" Version="4.0.0" />
|
||||
<PackageReference Include="ModernUO.Serialization.Generator" Version="4.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="ModernUO.Serialization.Annotations" Version="4.1.0" />
|
||||
<PackageReference Include="ModernUO.Serialization.Generator" Version="4.1.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Migrations/*.v*.json" />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue