diff --git a/HarmonySrc/GameVersionGuard.cs b/HarmonySrc/GameVersionGuard.cs
index 58d3d5f..8ecfe59 100644
--- a/HarmonySrc/GameVersionGuard.cs
+++ b/HarmonySrc/GameVersionGuard.cs
@@ -18,7 +18,7 @@ namespace NecromancerTome
///
/// ПОЧЕМУ Minor / 10. Игра хранит версию двузначным числом: 3.2.0 = 20, 3.2.1 = 21, 3.3.0 = 30
/// (VersionInformation печатает его как {Minor/10}.{Minor%10}). Проверка по десяткам пропускает
- /// любые хотфиксы 3.2.x и не пропускает 3.3 и всё, что будет после.
+ /// любые хотфиксы 3.3.x и не пропускает ни 3.2, ни 3.4 и всё, что будет после.
///
/// ВСЁ, ЧЕМ ПОЛЬЗУЕТСЯ ЭТОТ ФАЙЛ, СВЕРЕНО С ДЕКОМПИЛЯЦИЕЙ 3.3.0 (build 25485021): Constants,
/// VersionInformation, XUiC_MainMenu.OnOpen, XUiC_MessageBoxWindowGroup (класс совпадает целиком),
@@ -29,10 +29,10 @@ namespace NecromancerTome
///
public static class GameVersionGuard
{
- /// Под какую версию собран мод: Major и десяток Minor (3.2.x = 3 и 2).
+ /// Под какую версию собран мод: Major и десяток Minor (3.3.x = 3 и 3; сборка 1.4.x была под 3.2).
public const int SupportedMajor = 3;
- public const int SupportedMinorTens = 2;
- public const string SupportedText = "3.2";
+ public const int SupportedMinorTens = 3;
+ public const string SupportedText = "3.3";
private static bool? supported;
diff --git a/HarmonySrc/NecromancerBloodPatch.cs b/HarmonySrc/NecromancerBloodPatch.cs
index d0841ee..af901eb 100644
--- a/HarmonySrc/NecromancerBloodPatch.cs
+++ b/HarmonySrc/NecromancerBloodPatch.cs
@@ -130,17 +130,19 @@ namespace NecromancerTome
public static bool HasAnyKnife(EntityPlayerLocal player)
{
- return ContainsKnife(player.inventory?.GetSlots()) || ContainsKnife(player.bag?.GetSlots());
+ return ContainsKnife(player.inventory?.ItemGrid) || ContainsKnife(player.bag?.ItemGrid);
}
- private static bool ContainsKnife(ItemStack[] slots)
+ // В 3.3 у пояса и рюкзака нет GetSlots: предметы лежат в ItemStackGrid.
+ private static bool ContainsKnife(ItemStackGrid grid)
{
- if (slots == null)
+ if (grid == null)
{
return false;
}
- foreach (ItemStack stack in slots)
+ for (int i = 0; i < grid.Length; i++)
{
+ ItemStack stack = grid.GetItem(i);
if (stack == null || stack.IsEmpty())
{
continue;
diff --git a/HarmonySrc/PyramidWardPatch.cs b/HarmonySrc/PyramidWardPatch.cs
index ad04be9..b041868 100644
--- a/HarmonySrc/PyramidWardPatch.cs
+++ b/HarmonySrc/PyramidWardPatch.cs
@@ -207,13 +207,13 @@ namespace NecromancerTome
/// user's "как проще" - this is a brand-new feature, nothing to migrate from yet;
/// PooledBinaryReader.ReadBoolean() below has no such compile restriction, confirmed
/// separately, so Read() needs no equivalent workaround.
- public override void Write(PooledBinaryWriter _bw, TileEntity.StreamModeWrite _eStreamMode)
+ public override void Write(PooledBinaryWriter _bw, StreamModeWrite _eStreamMode)
{
base.Write(_bw, _eStreamMode);
PyramidWardWriteHelper.Write(_bw, EffectOn, ZoneShown);
}
- public override void Read(PooledBinaryReader _br, TileEntity.StreamModeRead _eStreamMode)
+ public override void Read(PooledBinaryReader _br, StreamModeRead _eStreamMode)
{
base.Read(_br, _eStreamMode);
EffectOn = _br.ReadBoolean();
diff --git a/HarmonySrc/SpatialVaultPatch.cs b/HarmonySrc/SpatialVaultPatch.cs
index fd0ba89..20fd7bf 100644
--- a/HarmonySrc/SpatialVaultPatch.cs
+++ b/HarmonySrc/SpatialVaultPatch.cs
@@ -139,7 +139,7 @@ namespace NecromancerTome
// the safety net for when that chain does not complete: opening the bracelet must
// never be what silently starts an empty vault over a saved one. Only then is a
// genuinely new bag created.
- bag = SpatialVaultPersistence.LastLoadedVault ?? new Bag(slotCount);
+ bag = SpatialVaultPersistence.LastLoadedVault ?? new Bag(new Vector2i(slotCount, 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null);
if (bag == SpatialVaultPersistence.LastLoadedVault)
{
Debug.Log("[NecromancerTome] SpatialVaultPatch: session cache was empty, adopted the last loaded vault (" +
@@ -149,13 +149,9 @@ namespace NecromancerTome
}
else if (bag.SlotCount < slotCount)
{
- // Grow, never shrink - the skill level only ever goes up, so this only ever
- // copies existing stacks into a bigger array, same shape
- // EntityLootContainer.SetContent itself uses when it needs to resize a bag.
- ItemStack[] oldSlots = bag.GetSlots();
- ItemStack[] newSlots = ItemStack.CreateArray(slotCount);
- Array.Copy(oldSlots, newSlots, oldSlots.Length);
- bag.SetSlots(newSlots);
+ // Grow, never shrink - the skill level only ever goes up. В 3.3 это Bag.Resize:
+ // ItemStackGrid.Resize переносит все стопки в новый массив и досоздаёт слоты.
+ bag.Resize(new Vector2i(slotCount, 1));
}
Debug.Log("[NecromancerTome] SpatialVaultPatch: owner=" + player.entityId + " opened vault, " + slotCount + " slots (Necromancy level " + level + ")");
diff --git a/HarmonySrc/SpatialVaultPersistence.cs b/HarmonySrc/SpatialVaultPersistence.cs
index 02e999e..15f1270 100644
--- a/HarmonySrc/SpatialVaultPersistence.cs
+++ b/HarmonySrc/SpatialVaultPersistence.cs
@@ -79,8 +79,16 @@ namespace NecromancerTome
{
/// Payload layout version, independent of the blob framing in
/// SpatialVaultBlobIO. An unknown version is skipped, not guessed at - the framing's
- /// explicit length means we can always step over a payload we do not understand.
- public const byte PayloadVersion = 1;
+ /// explicit length means we can always step over a payload we do not understand.
+ ///
+ /// 2 = игра 3.3 (мод 1.5.0): сумка пишется новым Bag.Write (свой байт версии 2 и
+ /// ItemStackGrid). 1 = игра 3.2, старый формат сумки; 3.3 читает его сам (Bag.readInto,
+ /// ветка версии < 2), поэтому хранилище из сейва 3.2 переезжает без потерь.
+ /// Номер поднят, чтобы сборка под 3.2 не приняла новый формат за свой.
+ public const byte PayloadVersion = 2;
+
+ /// Старейший номер, который эта сборка ещё читает.
+ public const byte OldestReadablePayloadVersion = 1;
/// What a PlayerDataFile carries. A class rather than a bare Bag because its mere
/// PRESENCE is information: "this file has been read/filled, and the answer - including a
@@ -147,30 +155,34 @@ namespace NecromancerTome
/// which is the part that had diagnostic value.
public static string lastSaveLogged;
- /// Builds the opaque payload SpatialVaultBlobIO wraps. Uses netstandard's own
- /// BinaryWriter over a MemoryStream, which is why Bag serialization can stay in this
- /// project instead of the satellite assembly.
+ /// Builds the opaque payload SpatialVaultBlobIO wraps. Заголовок (версия и
+ /// «есть ли сумка») пишется в MemoryStream напрямую, а сама сумка - через
+ /// PooledBinaryWriter поверх того же потока: в 3.3 Bag.Write принимает только его.
+ /// Сами Write-перегрузки PooledBinaryWriter из этого проекта не вызываются (см.
+ /// NecromancerTEPersistence.csproj), поэтому заголовок и не идёт через него.
public static byte[] BuildPayload(Bag _bag)
{
using (MemoryStream ms = new MemoryStream())
- using (BinaryWriter bw = new BinaryWriter(ms))
{
- bw.Write(PayloadVersion);
+ ms.WriteByte(PayloadVersion);
bool hasBag = _bag != null;
- bw.Write(hasBag);
+ ms.WriteByte(hasBag ? (byte)1 : (byte)0);
if (hasBag)
{
- // Plain BinaryWriter is enough: Bag.Write only demands a PooledBinaryWriter
- // when bag.preferences != null, and vault bags come from `new Bag(int)`, whose
- // constructor sets nothing but the item array.
- _bag.Write(bw);
+ using (PooledBinaryWriter pw = MemoryPools.poolBinaryWriter.AllocSync(_bReset: false))
+ {
+ pw.SetBaseStream(ms);
+ _bag.Write(pw, StreamModeWrite.Persistency);
+ pw.Flush();
+ }
}
- bw.Flush();
return ms.ToArray();
}
}
- /// Null when the payload holds no vault or is a version we do not know.
+ /// Null when the payload holds no vault. Незнакомый номер версии - исключение,
+ /// а НЕ null: null значит «хранилища нет», и следующее сохранение стёрло бы вещи.
+ /// Исключение ловит Read-постфикс и сохраняет байты нетронутыми (AttachRaw).
public static Bag ParsePayload(byte[] _payload)
{
if (_payload == null || _payload.Length == 0)
@@ -178,22 +190,25 @@ namespace NecromancerTome
return null;
}
using (MemoryStream ms = new MemoryStream(_payload, false))
- using (BinaryReader br = new BinaryReader(ms))
{
- byte version = br.ReadByte();
- if (version != PayloadVersion)
+ int version = ms.ReadByte();
+ if (version < OldestReadablePayloadVersion || version > PayloadVersion)
{
- Debug.LogWarning("[NecromancerTome] SpatialVaultPersistence: vault payload version " + version + ", expected " + PayloadVersion + " - skipped");
- return null;
+ throw new InvalidDataException("vault payload version " + version + ", this build reads " +
+ OldestReadablePayloadVersion + ".." + PayloadVersion);
}
- if (!br.ReadBoolean())
+ if (ms.ReadByte() != 1)
{
return null;
}
- // Bag.Read is the STATIC one and returns a new Bag; ReadInto is the instance
- // version. Symmetric with BuildPayload: preferences were written as absent, so no
- // PooledBinaryReader is needed here either.
- return Bag.Read(br);
+ // Сумка сама пишет свой номер формата, и Bag.readInto 3.3 понимает оба: и старый
+ // (из сейва 3.2), и новый. Место хранения - как у сумки дрона (то же окно), владельца нет:
+ // так же игра сама создаёт сумку из старого сейва (PlayerDataFile, legacyBag).
+ using (PooledBinaryReader pr = MemoryPools.poolBinaryReader.AllocSync(_bReset: false))
+ {
+ pr.SetBaseStream(ms);
+ return Bag.Read(pr, XUiC_ItemStack.StackLocationTypes.LootContainer, StreamModeRead.Persistency, null);
+ }
}
}
@@ -212,14 +227,14 @@ namespace NecromancerTome
{
return "NONE";
}
- ItemStack[] slots = _bag.GetSlots();
+ ItemStackGrid slots = _bag.ItemGrid;
StringBuilder sb = new StringBuilder();
sb.Append(_bag.SlotCount).Append(" slots, ").Append(_bag.GetUsedSlotCount()).Append(" used");
if (slots != null)
{
for (int i = 0; i < slots.Length; i++)
{
- ItemStack stack = slots[i];
+ ItemStack stack = slots.GetItem(i);
if (stack == null || stack.IsEmpty())
{
continue;
diff --git a/HarmonySrc/SpatialVaultPickupPatch.cs b/HarmonySrc/SpatialVaultPickupPatch.cs
index 6182e14..b10483c 100644
--- a/HarmonySrc/SpatialVaultPickupPatch.cs
+++ b/HarmonySrc/SpatialVaultPickupPatch.cs
@@ -457,16 +457,16 @@ namespace NecromancerTome
public static float SpendCharge(PickupJob _job)
{
ItemValue bracelet = _job.Bracelet;
- if (bracelet == null || bracelet.Modifications == null || _job.ChannelSeconds <= 0f)
+ if (bracelet == null || bracelet.ModificationCount == 0 || _job.ChannelSeconds <= 0f)
{
return 0f;
}
float spent = 0f;
bool emptied = false;
- for (int i = 0; i < bracelet.Modifications.Length; i++)
+ for (int i = 0; i < bracelet.ModificationCount; i++)
{
- ItemValue mod = bracelet.Modifications[i];
+ ItemValue mod = bracelet.GetModification(i);
if (mod == null || mod.IsEmpty())
{
continue;
@@ -497,7 +497,7 @@ namespace NecromancerTome
// ItemValue.None is what an empty mod slot holds - type 0, which is exactly
// what IsEmpty() tests for, so the slot reads as free to every other piece of
// code including the gate in Begin.
- bracelet.Modifications[i] = ItemValue.None;
+ bracelet.SetModification(i, ItemValue.None);
emptied = true;
Debug.Log("[NecromancerTome] SpatialVaultPickup: sphere in slot " + i +
" ran out (" + mod.UseTimes.ToString("0.#") + "/" + max +
@@ -585,7 +585,7 @@ namespace NecromancerTome
// a composite tile entity with a storage FEATURE rather than as their own class, so
// the question has to be asked of the feature - the same TryGetSelfOrFeature call the
// engine's own storage code uses.
- if (tileEntity.TryGetSelfOrFeature(out ITileEntityLootable lootable) && !lootable.IsEmpty())
+ if (tileEntity.TryGetSelfOrFeature(out TEFeatureStorage storage) && !storage.IsEmpty())
{
Deny(_player, MsgNotEmpty);
return false;
@@ -616,15 +616,12 @@ namespace NecromancerTome
if (!Patch_ItemActionEat_ExecuteAction_SpatialVault.PlayerVaults.TryGetValue(_player.entityId, out Bag bag))
{
- bag = SpatialVaultPersistence.LastLoadedVault ?? new Bag(slotCount);
+ bag = SpatialVaultPersistence.LastLoadedVault ?? new Bag(new Vector2i(slotCount, 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null);
Patch_ItemActionEat_ExecuteAction_SpatialVault.PlayerVaults[_player.entityId] = bag;
}
if (bag.SlotCount < slotCount)
{
- ItemStack[] oldSlots = bag.GetSlots();
- ItemStack[] newSlots = ItemStack.CreateArray(slotCount);
- System.Array.Copy(oldSlots, newSlots, oldSlots.Length);
- bag.SetSlots(newSlots);
+ bag.Resize(new Vector2i(slotCount, 1));
}
return bag;
}
diff --git a/NecromancerHarmony.dll b/NecromancerHarmony.dll
index c2e2a06..681260c 100644
Binary files a/NecromancerHarmony.dll and b/NecromancerHarmony.dll differ
diff --git a/NecromancerHarmony.pdb b/NecromancerHarmony.pdb
index aa1279e..512db90 100644
Binary files a/NecromancerHarmony.pdb and b/NecromancerHarmony.pdb differ
diff --git a/NecromancerTEPersistence.dll b/NecromancerTEPersistence.dll
index 2a1a9d3..c493e6e 100644
Binary files a/NecromancerTEPersistence.dll and b/NecromancerTEPersistence.dll differ