mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-10-02 06:49:55 -05:00
Add more event block repo types
Allow bigger window
This commit is contained in:
@@ -34,6 +34,14 @@ public sealed class SaveBlockAccessor9ZA(SAV9ZA sav) : SCBlockAccessor
|
||||
public EventWorkValueStorage WorkSpawn { get; } = new(sav, Block(sav, KEventWorkSpawn));
|
||||
public EventWorkFlagStorage Flags { get; } = new(sav, Block(sav, KEventFlagsOther));
|
||||
|
||||
public EventWorkValueStorageKey128 Report { get; } = new(sav, Block(sav, KEventReport));
|
||||
public EventWorkValueStorageKey128 Obstruction { get; } = new(sav, Block(sav, KObstruction));
|
||||
public EventWorkValueStorageKey192 FieldObjectInteractable { get; } = new(sav, Block(sav, KFieldObjectInteractable));
|
||||
|
||||
public EventWorkValueStorageKey128 Spawner2 { get; } = new(sav, Block(sav, KEventSpawner2)); // (u64-key, u64-bool, u64-struct)
|
||||
public EventWorkValueStorage InfiniteRank { get; } = new(sav, Block(sav, KEventInfiniteRank)); // (u64-key, u64-struct)
|
||||
public EventWorkValueStorageKey128 Spawner4 { get; } = new(sav, Block(sav, KEventSpawner4)); // (u64-key, u64-hash, u64-struct)
|
||||
|
||||
public MableStatus9a Mable { get; } = new(sav, Block(sav, KStatusMable));
|
||||
|
||||
private const uint KBox = 0x0d66012c; // Box Data
|
||||
@@ -64,12 +72,15 @@ public sealed class SaveBlockAccessor9ZA(SAV9ZA sav) : SCBlockAccessor
|
||||
|
||||
private const uint KEventCountTitle = 0x2C2C6964; // title_count (u64,u64)[64] - Player earned display titles
|
||||
private const uint KEventWorkSpawn = 0x53FD0223; // Overworld Spawner 0x46500 small values (u64,u64)[18000]
|
||||
// 7C896A83 0x2000 unused
|
||||
// B25E7EE5 0x400 unused
|
||||
// AF2165F0 0x3000 (u64,u64,value)
|
||||
private const uint KEventSpawner2 = 0x79ABCB0B; // (u64-key, u64-bool, u64-struct)
|
||||
private const uint KEventInfiniteRank = 0x7C896A83; // (u64-key, u64-struct)
|
||||
private const uint KEventSpawner4 = 0xD1A3FF7B; // (u64-key, u64-hash, u64-struct)
|
||||
// B25E7EE5 0x400 unused
|
||||
|
||||
private const uint KEventReport = 0xAF2165F0; // 0x3000 (u64,(s64,u64) value)
|
||||
private const uint KObstruction = 0x4C26C29B; // (u64, u64-state, u64-unused)[2000]
|
||||
private const uint KFieldObjectInteractable = 0x7147C953; // (u64,u64,u64,value)[5000] (mega crystal, prize medals)
|
||||
|
||||
private const uint KObstruction = 0x4C26C29B; // (u64, u64-state, u64-unused)[2000]
|
||||
|
||||
public const uint KTicketPointsZARoyale = 0x9A730DE1; // u32
|
||||
public const uint KTicketPointsZARoyaleInfinite = 0x1D7EE369; // u32
|
||||
|
||||
@@ -5,29 +5,41 @@
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Storage for event work values keyed by a 64-bit FNV-1a hash.
|
||||
/// Generic storage for event work values keyed by a fixed-size key and backed by a 64-bit value field.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Unmanaged value type stored alongside the hash. Backed by a 64-bit field.</typeparam>
|
||||
/// <typeparam name="TKey">Key type. Implementations serialize/deserialize to a fixed-size number of 64-bit chunks.</typeparam>
|
||||
/// <typeparam name="TValue">Unmanaged value type stored alongside the key. Backed by a 64-bit field.</typeparam>
|
||||
/// <param name="sav">Owning save file.</param>
|
||||
/// <param name="block">Underlying block providing the backing bytes.</param>
|
||||
public abstract class EventWorkStorage64<T>(SaveFile sav, SCBlock block) : SaveBlock<SaveFile>(sav, block.Raw),
|
||||
IEventValueStorage<T> where T : struct, IEquatable<T>
|
||||
public abstract class EventWorkStorage<TKey, TValue>(SaveFile sav, SCBlock block) : SaveBlock<SaveFile>(sav, block.Raw)
|
||||
where TKey : struct, IEquatable<TKey>
|
||||
where TValue : struct, IEquatable<TValue>
|
||||
{
|
||||
// Structure:
|
||||
// entry[] of u64 hash, T value
|
||||
// T is backed by a separate u64
|
||||
// Essentially, it is
|
||||
// (u64, u64)[Count];
|
||||
// Empty entries use an empty hash value instead of 0.
|
||||
// entry[] of key(u64 * N), value(u64)
|
||||
// Empty entries use an implementation-defined empty key value.
|
||||
// Default state is all entries initialized to "empty".
|
||||
|
||||
/// <summary>
|
||||
/// Size in bytes of a single entry: 8 bytes for the key hash + 8 bytes for the value.
|
||||
/// </summary>
|
||||
public const int ENTRY_SIZE = 16;
|
||||
private const ulong HashEmpty = FnvHash.HashEmpty;
|
||||
/// <summary> Number of 64-bit chunks used to serialize a key. </summary>
|
||||
protected abstract int KeyU64Count { get; }
|
||||
|
||||
/// <summary> Gets the empty key sentinel. </summary>
|
||||
protected abstract TKey EmptyKey { get; }
|
||||
|
||||
/// <summary> Reads the key at the specified index. </summary>
|
||||
protected abstract TKey ReadKey(int index);
|
||||
|
||||
/// <summary> Writes the key at the specified index. </summary>
|
||||
protected abstract void WriteKey(int index, TKey key);
|
||||
|
||||
/// <summary> Compares two keys for sorting. Return same semantics as <see cref="IComparer{T}.Compare(T, T)"/>. </summary>
|
||||
protected abstract int CompareKeys(TKey a, TKey b);
|
||||
|
||||
/// <summary> Size in bytes of a single entry: key bytes (8 * KeyU64Count) + 8 bytes for the value. </summary>
|
||||
protected int EntrySize => (KeyU64Count * sizeof(ulong)) + sizeof(ulong);
|
||||
|
||||
public int Count => Data.Length / EntrySize;
|
||||
|
||||
public int Count => Data.Length / ENTRY_SIZE;
|
||||
public int CountUsed => ScanCountUsed(Data);
|
||||
|
||||
private int ScanCountUsed(ReadOnlySpan<byte> data)
|
||||
@@ -35,61 +47,44 @@ private int ScanCountUsed(ReadOnlySpan<byte> data)
|
||||
int count = 0;
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var hash = ReadUInt64LittleEndian(data[GetOffset(i)..]);
|
||||
if (hash == 0)
|
||||
var key = ReadKey(i);
|
||||
if (IsEmpty(key))
|
||||
break;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static ulong Hash(ReadOnlySpan<char> name) => FnvHash.HashFnv1a_64(name);
|
||||
|
||||
public bool HasSpace(out int index) => (index = CountUsed) < Count;
|
||||
public bool HasSpace() => HasSpace(out _);
|
||||
public bool TryGetValue(ReadOnlySpan<char> name, out T value) => TryGetValue(Hash(name), out value);
|
||||
|
||||
public bool TryGetValue(ulong hash, out T value)
|
||||
{
|
||||
var index = GetIndex(hash);
|
||||
if (index == -1)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
value = GetValue(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected int GetOffset(int index)
|
||||
{
|
||||
if ((uint)index > Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(index), index, null);
|
||||
return index * ENTRY_SIZE;
|
||||
return index * EntrySize;
|
||||
}
|
||||
|
||||
public int GetIndex(ulong hash)
|
||||
protected bool IsEmpty(TKey key) => EqualityComparer<TKey>.Default.Equals(key, EmptyKey);
|
||||
|
||||
public bool HasSpace(out int index) => (index = CountUsed) < Count;
|
||||
public bool HasSpace() => HasSpace(out _);
|
||||
|
||||
public int GetIndex(TKey key)
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var ofs = i * ENTRY_SIZE;
|
||||
var entryHash = ReadUInt64LittleEndian(Data.Slice(ofs, 8));
|
||||
if (entryHash == hash)
|
||||
var entryKey = ReadKey(i);
|
||||
if (entryKey.Equals(key))
|
||||
return i;
|
||||
if (entryHash == HashEmpty)
|
||||
if (IsEmpty(entryKey))
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Clear(int index) => WriteEntry(Data[GetOffset(index)..], HashEmpty, 0);
|
||||
|
||||
private void WriteEntry(int index, ulong hash, ulong value) => WriteEntry(Data[GetOffset(index)..], hash, value);
|
||||
|
||||
private static void WriteEntry(Span<byte> data, ulong hash, ulong value)
|
||||
public void Clear(int index)
|
||||
{
|
||||
WriteUInt64LittleEndian(data, hash);
|
||||
WriteUInt64LittleEndian(data[8..], value);
|
||||
// Write empty key and zero value
|
||||
WriteKey(index, EmptyKey);
|
||||
WriteUInt64LittleEndian(Data.Slice(GetOffset(index) + (KeyU64Count * sizeof(ulong)), sizeof(ulong)), 0);
|
||||
}
|
||||
|
||||
public void Compress()
|
||||
@@ -98,16 +93,16 @@ public void Compress()
|
||||
int writePtr = 0;
|
||||
for (int readPtr = 0; readPtr < count; readPtr++)
|
||||
{
|
||||
var readOfs = GetOffset(readPtr);
|
||||
var entryHash = ReadUInt64LittleEndian(Data.Slice(readOfs, 8));
|
||||
if (entryHash == HashEmpty)
|
||||
var entryKey = ReadKey(readPtr);
|
||||
if (IsEmpty(entryKey))
|
||||
continue; // skip empty
|
||||
if (writePtr != readPtr)
|
||||
{
|
||||
var writeOfs = GetOffset(writePtr);
|
||||
var readOfs = GetOffset(readPtr);
|
||||
// Move entry
|
||||
var dest = Data.Slice(writeOfs, ENTRY_SIZE);
|
||||
var src = Data.Slice(readOfs, ENTRY_SIZE);
|
||||
var dest = Data.Slice(writeOfs, EntrySize);
|
||||
var src = Data.Slice(readOfs, EntrySize);
|
||||
src.CopyTo(dest);
|
||||
// Clear old entry
|
||||
Clear(readPtr);
|
||||
@@ -119,50 +114,203 @@ public void Compress()
|
||||
public void Sort()
|
||||
{
|
||||
Compress();
|
||||
var entries = new (ulong Hash, ulong Value)[CountUsed];
|
||||
var used = CountUsed;
|
||||
var entries = new (TKey Key, ulong Value)[used];
|
||||
|
||||
// Gather used entries.
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
var data = Data[GetOffset(i)..];
|
||||
var entryHash = ReadUInt64LittleEndian(data);
|
||||
var entryValue = ReadUInt64LittleEndian(data[8..]);
|
||||
entries[i] = (entryHash, entryValue);
|
||||
var key = ReadKey(i);
|
||||
var value = ReadUInt64LittleEndian(data.Slice(KeyU64Count * sizeof(ulong), sizeof(ulong)));
|
||||
entries[i] = (key, value);
|
||||
}
|
||||
|
||||
Array.Sort(entries, Comparer<(ulong Hash, ulong Value)>.Create((a, b) => a.Hash.CompareTo(b.Hash)));
|
||||
Array.Sort(entries, Comparer<(TKey Key, ulong Value)>.Create((a, b) => CompareKeys(a.Key, b.Key)));
|
||||
|
||||
// Write back
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
WriteEntry(i, entries[i].Hash, entries[i].Value);
|
||||
{
|
||||
WriteKey(i, entries[i].Key);
|
||||
WriteUInt64LittleEndian(Data.Slice(GetOffset(i) + (KeyU64Count * sizeof(ulong)), sizeof(ulong)), entries[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetKey(int index) => ReadUInt64LittleEndian(Data.Slice(GetOffset(index), 8));
|
||||
public void SetKey(int index, ulong key) => WriteUInt64LittleEndian(Data.Slice(GetOffset(index), 8), key);
|
||||
public abstract T GetValue(int index);
|
||||
public abstract void SetValue(int index, T value);
|
||||
public abstract TValue GetValue(int index);
|
||||
public abstract void SetValue(int index, TValue value);
|
||||
|
||||
public T this[ReadOnlySpan<char> name]
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
var index = GetIndex(key);
|
||||
if (index == -1)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
value = GetValue(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetValue(TKey key, TValue value)
|
||||
{
|
||||
var index = GetIndex(key);
|
||||
if (index == -1 && !HasSpace(out index))
|
||||
throw new InvalidOperationException("No space available to add new entry.");
|
||||
// Only ensure the key exists; value writing is done via SetValue(index, ...)
|
||||
WriteKey(index, key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Storage for event work values keyed by a 64-bit FNV-1a hash.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Unmanaged value type stored alongside the hash. Backed by a 64-bit field.</typeparam>
|
||||
/// <param name="sav">Owning save file.</param>
|
||||
/// <param name="block">Underlying block providing the backing bytes.</param>
|
||||
public abstract class EventWorkStorage64<TValue>(SaveFile sav, SCBlock block) : EventWorkStorage<ulong, TValue>(sav, block),
|
||||
IEventValueStorage<TValue> where TValue : struct, IEquatable<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// Size in bytes of a single entry: 8 bytes for the key hash + 8 bytes for the value.
|
||||
/// </summary>
|
||||
public const int ENTRY_SIZE = 16;
|
||||
private const ulong HashEmpty = FnvHash.HashEmpty;
|
||||
|
||||
protected override int KeyU64Count => 1;
|
||||
protected override ulong EmptyKey => HashEmpty;
|
||||
|
||||
protected override int CompareKeys(ulong a, ulong b) => a.CompareTo(b);
|
||||
|
||||
protected override ulong ReadKey(int index)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
return ReadUInt64LittleEndian(Data.Slice(ofs, sizeof(ulong)));
|
||||
}
|
||||
|
||||
protected override void WriteKey(int index, ulong key)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
WriteUInt64LittleEndian(Data.Slice(ofs, sizeof(ulong)), key);
|
||||
}
|
||||
|
||||
private static ulong Hash(ReadOnlySpan<char> name) => FnvHash.HashFnv1a_64(name);
|
||||
|
||||
public bool TryGetValue(ReadOnlySpan<char> name, out TValue value) => TryGetValue(Hash(name), out value);
|
||||
|
||||
public ulong GetKey(int index) => ReadKey(index);
|
||||
public void SetKey(int index, ulong key) => WriteKey(index, key);
|
||||
|
||||
public TValue this[ReadOnlySpan<char> name]
|
||||
{
|
||||
get => TryGetValue(name, out var val) ? val : throw new KeyNotFoundException($"No entry found for name '{name.ToString()}'");
|
||||
set => SetValue(name, value);
|
||||
}
|
||||
|
||||
public T this[ulong hash]
|
||||
public TValue this[ulong hash]
|
||||
{
|
||||
get => TryGetValue(hash, out var val) ? val : throw new KeyNotFoundException($"No entry found for hash '{hash:X16}'");
|
||||
set => SetValue(hash, value);
|
||||
}
|
||||
|
||||
public void SetValue(ulong hash, T value)
|
||||
public void SetValue(ReadOnlySpan<char> name, TValue value) => SetValue(Hash(name), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Storage for event work values keyed by a 128-bit composite FNV-1a hash (two 64-bit parts).
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Unmanaged value type stored alongside the key. Backed by a 64-bit field.</typeparam>
|
||||
/// <param name="sav">Owning save file.</param>
|
||||
/// <param name="block">Underlying block providing the backing bytes.</param>
|
||||
public abstract class EventWorkStorageKey128<TValue>(SaveFile sav, SCBlock block) : EventWorkStorage<(ulong A, ulong B), TValue>(sav, block)
|
||||
where TValue : struct, IEquatable<TValue>
|
||||
{
|
||||
protected override int KeyU64Count => 2;
|
||||
protected override (ulong A, ulong B) EmptyKey => (FnvHash.HashEmpty, FnvHash.HashEmpty);
|
||||
|
||||
protected override int CompareKeys((ulong A, ulong B) a, (ulong A, ulong B) b)
|
||||
{
|
||||
var index = GetIndex(hash);
|
||||
if (index == -1 && !HasSpace(out index))
|
||||
throw new InvalidOperationException("No space available to add new entry.");
|
||||
WriteEntry(index, hash, 0);
|
||||
int cmp = a.A.CompareTo(b.A);
|
||||
return cmp != 0 ? cmp : a.B.CompareTo(b.B);
|
||||
}
|
||||
|
||||
public void SetValue(ReadOnlySpan<char> name, T value) => SetValue(Hash(name), value);
|
||||
protected override (ulong A, ulong B) ReadKey(int index)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
var a = ReadUInt64LittleEndian(Data.Slice(ofs + (0 * sizeof(ulong)), sizeof(ulong)));
|
||||
var b = ReadUInt64LittleEndian(Data.Slice(ofs + (1 * sizeof(ulong)), sizeof(ulong)));
|
||||
return (a, b);
|
||||
}
|
||||
|
||||
protected override void WriteKey(int index, (ulong A, ulong B) key)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
WriteUInt64LittleEndian(Data.Slice(ofs + (0 * sizeof(ulong)), sizeof(ulong)), key.A);
|
||||
WriteUInt64LittleEndian(Data.Slice(ofs + (1 * sizeof(ulong)), sizeof(ulong)), key.B);
|
||||
}
|
||||
|
||||
private static ulong Hash(ReadOnlySpan<char> name) => FnvHash.HashFnv1a_64(name);
|
||||
|
||||
// Helper methods for convenience
|
||||
public bool TryGetValue(ReadOnlySpan<char> a, ReadOnlySpan<char> b, out TValue value)
|
||||
=> TryGetValue((Hash(a), Hash(b)), out value);
|
||||
|
||||
public void SetValue(ReadOnlySpan<char> a, ReadOnlySpan<char> b, TValue value)
|
||||
=> SetValue((Hash(a), Hash(b)), value);
|
||||
|
||||
public (ulong A, ulong B) GetKey(int index) => ReadKey(index);
|
||||
public void SetKey(int index, (ulong A, ulong B) key) => WriteKey(index, key);
|
||||
public void SetKey(int index, ulong a, ulong b) => WriteKey(index, (a, b));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Storage for event work values keyed by a 192-bit composite FNV-1a hash (three 64-bit parts).
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Unmanaged value type stored alongside the key. Backed by a 64-bit field.</typeparam>
|
||||
/// <param name="sav">Owning save file.</param>
|
||||
/// <param name="block">Underlying block providing the backing bytes.</param>
|
||||
public abstract class EventWorkStorageKey192<TValue>(SaveFile sav, SCBlock block) : EventWorkStorage<(ulong A, ulong B, ulong C), TValue>(sav, block)
|
||||
where TValue : struct, IEquatable<TValue>
|
||||
{
|
||||
protected override int KeyU64Count => 3;
|
||||
protected override (ulong A, ulong B, ulong C) EmptyKey => (FnvHash.HashEmpty, FnvHash.HashEmpty, FnvHash.HashEmpty);
|
||||
|
||||
protected override int CompareKeys((ulong A, ulong B, ulong C) a, (ulong A, ulong B, ulong C) b)
|
||||
{
|
||||
int cmp = a.A.CompareTo(b.A);
|
||||
if (cmp != 0) return cmp;
|
||||
cmp = a.B.CompareTo(b.B);
|
||||
return cmp != 0 ? cmp : a.C.CompareTo(b.C);
|
||||
}
|
||||
|
||||
protected override (ulong A, ulong B, ulong C) ReadKey(int index)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
var a = ReadUInt64LittleEndian(Data.Slice(ofs + (0 * sizeof(ulong)), sizeof(ulong)));
|
||||
var b = ReadUInt64LittleEndian(Data.Slice(ofs + (1 * sizeof(ulong)), sizeof(ulong)));
|
||||
var c = ReadUInt64LittleEndian(Data.Slice(ofs + (2 * sizeof(ulong)), sizeof(ulong)));
|
||||
return (a, b, c);
|
||||
}
|
||||
|
||||
protected override void WriteKey(int index, (ulong A, ulong B, ulong C) key)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
WriteUInt64LittleEndian(Data.Slice(ofs + (0 * sizeof(ulong)), sizeof(ulong)), key.A);
|
||||
WriteUInt64LittleEndian(Data.Slice(ofs + (1 * sizeof(ulong)), sizeof(ulong)), key.B);
|
||||
WriteUInt64LittleEndian(Data.Slice(ofs + (2 * sizeof(ulong)), sizeof(ulong)), key.C);
|
||||
}
|
||||
|
||||
private static ulong Hash(ReadOnlySpan<char> name) => FnvHash.HashFnv1a_64(name);
|
||||
|
||||
// Helper methods for convenience
|
||||
public bool TryGetValue(ReadOnlySpan<char> a, ReadOnlySpan<char> b, ReadOnlySpan<char> c, out TValue value)
|
||||
=> TryGetValue((Hash(a), Hash(b), Hash(c)), out value);
|
||||
|
||||
public void SetValue(ReadOnlySpan<char> a, ReadOnlySpan<char> b, ReadOnlySpan<char> c, TValue value)
|
||||
=> SetValue((Hash(a), Hash(b), Hash(c)), value);
|
||||
|
||||
public (ulong A, ulong B, ulong C) GetKey(int index) => ReadKey(index);
|
||||
public void SetKey(int index, (ulong A, ulong B, ulong C) key) => WriteKey(index, key);
|
||||
public void SetKey(int index, ulong a, ulong b, ulong c) => WriteKey(index, (a, b, c));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -205,3 +353,53 @@ public override void SetValue(int index, ulong value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event work storage specialized for 64-bit integer values.
|
||||
/// </summary>
|
||||
/// <param name="sav">Owning save file.</param>
|
||||
/// <param name="block">Underlying block providing the backing bytes.</param>
|
||||
public abstract class EventWorkTupleStorage<T1, T2>(SAV9ZA sav, SCBlock block) : EventWorkStorageKey128<(T1, T2)>(sav, block)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event work storage specialized for 64-bit integer values with 128-bit keys.
|
||||
/// </summary>
|
||||
/// <param name="sav">Owning save file.</param>
|
||||
/// <param name="block">Underlying block providing the backing bytes.</param>
|
||||
public sealed class EventWorkValueStorageKey128(SAV9ZA sav, SCBlock block) : EventWorkStorageKey128<ulong>(sav, block)
|
||||
{
|
||||
public override ulong GetValue(int index)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
return ReadUInt64LittleEndian(Data.Slice(ofs + (KeyU64Count * sizeof(ulong)), sizeof(ulong)));
|
||||
}
|
||||
|
||||
public override void SetValue(int index, ulong value)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
WriteUInt64LittleEndian(Data.Slice(ofs + (KeyU64Count * sizeof(ulong)), sizeof(ulong)), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event work storage specialized for 64-bit integer values with 192-bit keys.
|
||||
/// </summary>
|
||||
/// <param name="sav">Owning save file.</param>
|
||||
/// <param name="block">Underlying block providing the backing bytes.</param>
|
||||
public sealed class EventWorkValueStorageKey192(SAV9ZA sav, SCBlock block) : EventWorkStorageKey192<ulong>(sav, block)
|
||||
{
|
||||
public override ulong GetValue(int index)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
return ReadUInt64LittleEndian(Data.Slice(ofs + (KeyU64Count * sizeof(ulong)), sizeof(ulong)));
|
||||
}
|
||||
|
||||
public override void SetValue(int index, ulong value)
|
||||
{
|
||||
var ofs = GetOffset(index);
|
||||
WriteUInt64LittleEndian(Data.Slice(ofs + (KeyU64Count * sizeof(ulong)), sizeof(ulong)), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,34 +15,132 @@ public interface IEventWorkGrid
|
||||
void Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immutable helper for name/hash lookup and reverse lookup.
|
||||
/// </summary>
|
||||
public sealed class EventWorkLookup(Dictionary<ulong, string> forward)
|
||||
{
|
||||
private readonly Dictionary<string, ulong> _reverse = forward.ToDictionary(z => z.Value, z => z.Key);
|
||||
|
||||
public string GetName(ulong hash)
|
||||
{
|
||||
if (forward.TryGetValue(hash, out var name))
|
||||
return name;
|
||||
return hash.ToString("X16");
|
||||
}
|
||||
|
||||
public ulong GetHash(string nameOrHex)
|
||||
{
|
||||
if (_reverse.TryGetValue(nameOrHex, out var h))
|
||||
return h;
|
||||
if (ulong.TryParse(nameOrHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed))
|
||||
return parsed;
|
||||
return FnvHash.HashFnv1a_64(nameOrHex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class with shared UI helpers and wiring for EventWork grids.
|
||||
/// </summary>
|
||||
public abstract record EventWorkGridBase(SplitContainer Container, DataGridView Grid, TextBox Search)
|
||||
: IEventWorkGrid
|
||||
{
|
||||
protected static (SplitContainer Container, TextBox Search) CreateContainerCommon()
|
||||
{
|
||||
var container = new SplitContainer
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Orientation = Orientation.Horizontal,
|
||||
FixedPanel = FixedPanel.Panel1,
|
||||
IsSplitterFixed = true,
|
||||
Margin = new Padding(0),
|
||||
};
|
||||
|
||||
var search = new TextBox
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Margin = new Padding(0),
|
||||
PlaceholderText = "Search...",
|
||||
};
|
||||
container.Panel1.Controls.Add(search);
|
||||
|
||||
var topHeight = Math.Max(search.PreferredHeight + 4, 24);
|
||||
container.Panel1MinSize = topHeight;
|
||||
container.SplitterDistance = topHeight;
|
||||
return (container, search);
|
||||
}
|
||||
|
||||
protected static DataGridViewTextBoxColumn MakeIndexColumn(Font font) => new()
|
||||
{
|
||||
Name = "Index",
|
||||
HeaderText = "Index",
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
|
||||
Width = 40,
|
||||
ReadOnly = true,
|
||||
};
|
||||
|
||||
protected static DataGridViewTextBoxColumn MakeKeyColumn(Font font, string title = "Name") => new()
|
||||
{
|
||||
Name = title,
|
||||
HeaderText = title,
|
||||
DefaultCellStyle = new DataGridViewCellStyle
|
||||
{
|
||||
Font = font,
|
||||
Alignment = DataGridViewContentAlignment.MiddleLeft,
|
||||
},
|
||||
};
|
||||
|
||||
protected static DataGridViewTextBoxColumn MakeValueNumberColumn(Font font, string title) => new()
|
||||
{
|
||||
Name = title,
|
||||
HeaderText = title,
|
||||
DefaultCellStyle = new DataGridViewCellStyle
|
||||
{
|
||||
Font = font,
|
||||
Alignment = DataGridViewContentAlignment.MiddleRight,
|
||||
NullValue = 0UL,
|
||||
},
|
||||
};
|
||||
|
||||
protected static DataGridViewCheckBoxColumn MakeValueBoolColumn(Font font, string title) => new()
|
||||
{
|
||||
Name = title,
|
||||
HeaderText = title,
|
||||
DefaultCellStyle = new DataGridViewCellStyle
|
||||
{
|
||||
Font = font,
|
||||
Alignment = DataGridViewContentAlignment.MiddleCenter,
|
||||
NullValue = false,
|
||||
},
|
||||
};
|
||||
|
||||
public abstract void Load();
|
||||
public abstract void Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper that creates and manages a DataGridView for editing an <see cref="EventWorkStorage64{T}"/> block.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Value type for the storage. Use bool for flags, ulong for values.</typeparam>
|
||||
public sealed class EventWorkGrid64<T> : IEventWorkGrid where T : struct, IEquatable<T>
|
||||
public sealed record EventWorkGrid64<T> : EventWorkGridBase where T : struct, IEquatable<T>
|
||||
{
|
||||
private readonly EventWorkStorage64<T> Storage;
|
||||
private readonly Dictionary<ulong, string> Lookup;
|
||||
private readonly DoubleBufferedDataGridView Grid;
|
||||
private readonly SplitContainer Container;
|
||||
private readonly TextBox SearchBox;
|
||||
private readonly EventWorkLookup Names;
|
||||
|
||||
private const int ColumnIndex = 0;
|
||||
private const int ColumnValue = 1;
|
||||
private const int ColumnName = 2;
|
||||
|
||||
private EventWorkGrid64(EventWorkStorage64<T> storage, Dictionary<ulong, string> lookup, SplitContainer container, DoubleBufferedDataGridView grid, TextBox search)
|
||||
private EventWorkGrid64(EventWorkStorage64<T> storage, EventWorkLookup names, SplitContainer container, DataGridView grid, TextBox search)
|
||||
: base(container, grid, search)
|
||||
{
|
||||
Storage = storage;
|
||||
Lookup = lookup;
|
||||
Container = container;
|
||||
Grid = grid;
|
||||
SearchBox = search;
|
||||
Names = names;
|
||||
|
||||
if (typeof(T) == typeof(ulong))
|
||||
Grid.CellValidated += ValidateU64;
|
||||
|
||||
SearchBox.TextChanged += (_, _) => ApplyFilter(SearchBox.Text);
|
||||
Search.TextChanged += (_, _) => ApplyFilter(Search.Text);
|
||||
|
||||
ToggleAutoSize(true);
|
||||
}
|
||||
@@ -62,68 +160,22 @@ private void ValidateU64(object? sender, DataGridViewCellEventArgs e)
|
||||
var row = Grid.Rows[e.RowIndex];
|
||||
var cell = row.Cells[e.ColumnIndex];
|
||||
var text = cell.Value?.ToString();
|
||||
if (ulong.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _))
|
||||
if (ulong.TryParse(text, CultureInfo.InvariantCulture, out _))
|
||||
return;
|
||||
|
||||
WinFormsUtil.Alert("Please enter a valid unsigned integer value.");
|
||||
// Revert to the original stored value.
|
||||
// Rather than use RowIndex, which may have changed due to sorting, use the Index cell value.
|
||||
var index = Convert.ToInt32(row.Cells[ColumnIndex].Value);
|
||||
var saved = Storage.GetValue(index);
|
||||
cell.Value = saved.ToString();
|
||||
}
|
||||
|
||||
public static EventWorkGrid64<bool> CreateFlags(TabPage host, EventWorkFlagStorage storage, Dictionary<ulong, string> lookup)
|
||||
public static EventWorkGrid64<bool> CreateFlags(TabPage host, EventWorkFlagStorage storage, EventWorkLookup names)
|
||||
{
|
||||
var (container, grid, search) = CreateContainer(true);
|
||||
var (container, search) = CreateContainerCommon();
|
||||
host.Controls.Clear();
|
||||
host.Controls.Add(container);
|
||||
return new EventWorkGrid64<bool>(storage, lookup, container, grid, search);
|
||||
}
|
||||
|
||||
public static EventWorkGrid64<ulong> CreateValues(TabPage host, EventWorkValueStorage storage, Dictionary<ulong, string> lookup)
|
||||
{
|
||||
var (container, grid, search) = CreateContainer(false);
|
||||
host.Controls.Clear();
|
||||
host.Controls.Add(container);
|
||||
return new EventWorkGrid64<ulong>(storage, lookup, container, grid, search);
|
||||
}
|
||||
|
||||
private static (SplitContainer Container, DoubleBufferedDataGridView Grid, TextBox Search) CreateContainer(bool isBoolean)
|
||||
{
|
||||
var container = new SplitContainer
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Orientation = Orientation.Horizontal,
|
||||
FixedPanel = FixedPanel.Panel1,
|
||||
IsSplitterFixed = true,
|
||||
Margin = new Padding(0),
|
||||
};
|
||||
|
||||
var search = new TextBox
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
Margin = new Padding(0),
|
||||
PlaceholderText = "Search...",
|
||||
};
|
||||
|
||||
// Default row font to Courier New (match designer)
|
||||
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||
var grid = CreateGrid(isBoolean, font);
|
||||
|
||||
container.Panel1.Controls.Add(search);
|
||||
container.Panel2.Controls.Add(grid);
|
||||
|
||||
// Size the top panel to the search box height, leave rest for grid.
|
||||
var topHeight = Math.Max(search.PreferredHeight + 4, 24);
|
||||
container.Panel1MinSize = topHeight;
|
||||
container.SplitterDistance = topHeight;
|
||||
|
||||
return (container, grid, search);
|
||||
}
|
||||
|
||||
private static DoubleBufferedDataGridView CreateGrid(bool isBoolean, Font font)
|
||||
{
|
||||
var dgv = new DoubleBufferedDataGridView
|
||||
{
|
||||
AllowUserToAddRows = false,
|
||||
@@ -138,62 +190,38 @@ private static DoubleBufferedDataGridView CreateGrid(bool isBoolean, Font font)
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
|
||||
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
|
||||
};
|
||||
|
||||
var colIndex = GetColumnIndex("Index", font);
|
||||
var colName = GetColumnName("Name", font);
|
||||
var colValue = GetColumnValue("Value", font, isBoolean);
|
||||
dgv.Columns.AddRange(colIndex, colValue, colName);
|
||||
return dgv;
|
||||
dgv.Columns.AddRange(MakeIndexColumn(font), MakeValueBoolColumn(font, "Value"), MakeKeyColumn(font));
|
||||
container.Panel2.Controls.Add(dgv);
|
||||
return new EventWorkGrid64<bool>(storage, names, container, dgv, search);
|
||||
}
|
||||
|
||||
private static DataGridViewTextBoxColumn GetColumnIndex(string title, Font font) => new()
|
||||
public static EventWorkGrid64<ulong> CreateValues(TabPage host, EventWorkValueStorage storage, EventWorkLookup names)
|
||||
{
|
||||
Name = title,
|
||||
HeaderText = title,
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
|
||||
Width = 40,
|
||||
ReadOnly = true,
|
||||
};
|
||||
var (container, search) = CreateContainerCommon();
|
||||
host.Controls.Clear();
|
||||
host.Controls.Add(container);
|
||||
|
||||
private static DataGridViewTextBoxColumn GetColumnName(string title, Font font) => new()
|
||||
{
|
||||
Name = title,
|
||||
HeaderText = title,
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
|
||||
};
|
||||
|
||||
private static DataGridViewColumn GetColumnValue(string title, Font font, bool isBoolean)
|
||||
{
|
||||
if (isBoolean)
|
||||
return GetValueColumnBool(title, font);
|
||||
return GetValueColumnNumber(title, font);
|
||||
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||
var dgv = new DoubleBufferedDataGridView
|
||||
{
|
||||
AllowUserToAddRows = false,
|
||||
AllowUserToDeleteRows = false,
|
||||
AllowUserToOrderColumns = true,
|
||||
AllowUserToResizeRows = false,
|
||||
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells,
|
||||
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None,
|
||||
Dock = DockStyle.Fill,
|
||||
RowHeadersVisible = false,
|
||||
Margin = new Padding(0),
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
|
||||
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
|
||||
};
|
||||
dgv.Columns.AddRange(MakeIndexColumn(font), MakeValueNumberColumn(font, "Value"), MakeKeyColumn(font));
|
||||
container.Panel2.Controls.Add(dgv);
|
||||
return new EventWorkGrid64<ulong>(storage, names, container, dgv, search);
|
||||
}
|
||||
|
||||
private static DataGridViewTextBoxColumn GetValueColumnNumber(string title, Font font) => new()
|
||||
{
|
||||
Name = title,
|
||||
HeaderText = title,
|
||||
DefaultCellStyle = new DataGridViewCellStyle
|
||||
{
|
||||
Font = font,
|
||||
Alignment = DataGridViewContentAlignment.MiddleRight,
|
||||
NullValue = 0,
|
||||
},
|
||||
};
|
||||
|
||||
private static DataGridViewCheckBoxColumn GetValueColumnBool(string title, Font font) => new()
|
||||
{
|
||||
Name = title,
|
||||
HeaderText = title,
|
||||
DefaultCellStyle = new DataGridViewCellStyle
|
||||
{
|
||||
Font = font,
|
||||
Alignment = DataGridViewContentAlignment.MiddleCenter,
|
||||
NullValue = false,
|
||||
},
|
||||
};
|
||||
|
||||
public void Load()
|
||||
public override void Load()
|
||||
{
|
||||
var rows = Grid.Rows;
|
||||
rows.Clear();
|
||||
@@ -203,7 +231,7 @@ public void Load()
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var hash = Storage.GetKey(i);
|
||||
var name = GetNameDisplay(hash);
|
||||
var name = Names.GetName(hash);
|
||||
var value = Storage.GetValue(i);
|
||||
|
||||
var row = rows[i];
|
||||
@@ -214,17 +242,6 @@ public void Load()
|
||||
}
|
||||
}
|
||||
|
||||
private string GetNameDisplay(ulong hash) => GetNameDisplay(hash, Lookup);
|
||||
|
||||
public static string GetNameDisplay(ulong hash, IReadOnlyDictionary<ulong, string> lookup)
|
||||
{
|
||||
if (lookup.TryGetValue(hash, out var name))
|
||||
return name;
|
||||
if (hash == FnvHash.HashEmpty)
|
||||
return string.Empty;
|
||||
return hash.ToString("X16");
|
||||
}
|
||||
|
||||
private void ApplyFilter(string text)
|
||||
{
|
||||
ToggleAutoSize(false);
|
||||
@@ -243,7 +260,7 @@ private void ApplyFilter(string text)
|
||||
ToggleAutoSize(true);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
public override void Save()
|
||||
{
|
||||
var rows = Grid.Rows;
|
||||
var count = Math.Min(Storage.Count, rows.Count);
|
||||
@@ -254,7 +271,7 @@ public void Save()
|
||||
try
|
||||
{
|
||||
var name = cells[ColumnName].Value?.ToString() ?? string.Empty;
|
||||
var hash = GetHash(name.Trim());
|
||||
var hash = Names.GetHash(name.Trim());
|
||||
var valObj = cells[ColumnValue].Value;
|
||||
|
||||
if (typeof(T) == typeof(bool))
|
||||
@@ -269,10 +286,8 @@ public void Save()
|
||||
}
|
||||
else
|
||||
{
|
||||
// Should not happen for current use cases.
|
||||
Storage.SetValue(i, (T)valObj!);
|
||||
}
|
||||
// Set hash last so that the entry only updates if value parsing succeeded.
|
||||
Storage.SetKey(i, hash);
|
||||
}
|
||||
catch
|
||||
@@ -280,24 +295,464 @@ public void Save()
|
||||
// Ignore invalid row
|
||||
}
|
||||
}
|
||||
Storage.Compress(); // Ensure the user didn't sneak in empty rows. There should be no gaps.
|
||||
}
|
||||
|
||||
private ulong GetHash(string name)
|
||||
{
|
||||
// Generally, the Lookup will be empty. The user can enter an arbitrary flag name string or hash, and we should recognize both.
|
||||
// If a recognized name is entered, use the known hash.
|
||||
// If an unrecognized name is entered, check if it's a hex number and use that as the hash.
|
||||
// Otherwise, just consider it as a name and hash it.
|
||||
|
||||
// O(n) reverse lookup is acceptable for a Saving operation.
|
||||
var match = Lookup.FirstOrDefault(z => z.Value == name);
|
||||
if (match.Value != null) // default if not found
|
||||
return match.Key;
|
||||
|
||||
if (ulong.TryParse(name, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hash))
|
||||
return hash;
|
||||
|
||||
return FnvHash.HashFnv1a_64(name);
|
||||
Storage.Compress();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grid for EventWorkStorage128 with one key columns and two values.
|
||||
/// </summary>
|
||||
public sealed record EventWorkGridTuple : EventWorkGridBase
|
||||
{
|
||||
private readonly EventWorkValueStorageKey128 Storage;
|
||||
private readonly EventWorkLookup Names;
|
||||
|
||||
private const int ColumnIndex = 0;
|
||||
private const int ColumnKey = 1;
|
||||
private const int ColumnValue1 = 2;
|
||||
private const int ColumnValue2 = 3;
|
||||
|
||||
private EventWorkGridTuple(EventWorkValueStorageKey128 storage, EventWorkLookup names, SplitContainer container, DataGridView grid, TextBox search)
|
||||
: base(container, grid, search)
|
||||
{
|
||||
Storage = storage;
|
||||
Names = names;
|
||||
|
||||
Grid.CellValueChanged += ValidateCell;
|
||||
Search.TextChanged += (_, _) => ApplyFilter(Search.Text);
|
||||
ToggleAutoSize(true);
|
||||
}
|
||||
|
||||
public static EventWorkGridTuple CreateValues(TabPage host, EventWorkValueStorageKey128 storage, EventWorkLookup names)
|
||||
{
|
||||
var (container, search) = CreateContainerCommon();
|
||||
host.Controls.Clear();
|
||||
host.Controls.Add(container);
|
||||
|
||||
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||
var dgv = new DoubleBufferedDataGridView
|
||||
{
|
||||
AllowUserToAddRows = false,
|
||||
AllowUserToDeleteRows = false,
|
||||
AllowUserToOrderColumns = true,
|
||||
AllowUserToResizeRows = false,
|
||||
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells,
|
||||
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None,
|
||||
Dock = DockStyle.Fill,
|
||||
RowHeadersVisible = false,
|
||||
Margin = new Padding(0),
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
|
||||
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
|
||||
};
|
||||
var c1 = MakeIndexColumn(font);
|
||||
var c2 = MakeKeyColumn(font, "Key A");
|
||||
var v1 = MakeValueNumberColumn(font, "Value 1");
|
||||
var v2 = MakeValueNumberColumn(font, "Value 2");
|
||||
|
||||
dgv.Columns.AddRange(c1, c2, v1, v2);
|
||||
container.Panel2.Controls.Add(dgv);
|
||||
return new EventWorkGridTuple(storage, names, container, dgv, search);
|
||||
}
|
||||
|
||||
private void ToggleAutoSize(bool state)
|
||||
{
|
||||
var columns = Grid.Columns;
|
||||
columns[ColumnIndex].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.ColumnHeader : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnKey].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnValue1].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnValue2].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
}
|
||||
|
||||
private void ValidateCell(object? sender, DataGridViewCellEventArgs e)
|
||||
{
|
||||
if (e.ColumnIndex is ColumnValue1 or ColumnValue2)
|
||||
{
|
||||
var row = Grid.Rows[e.RowIndex];
|
||||
var cell = row.Cells[e.ColumnIndex];
|
||||
var text = cell.Value?.ToString() ?? string.Empty;
|
||||
if (long.TryParse(text, CultureInfo.InvariantCulture, out _))
|
||||
return;
|
||||
WinFormsUtil.Alert("Please enter a valid value.");
|
||||
var i = Convert.ToInt32(row.Cells[ColumnIndex].Value);
|
||||
var (a, b) = Storage.GetKey(i);
|
||||
if (e.ColumnIndex == ColumnKey)
|
||||
cell.Value = Names.GetName(a);
|
||||
else if (e.ColumnIndex == ColumnValue1)
|
||||
cell.Value = ((long)b).ToString();
|
||||
else
|
||||
cell.Value = Storage.GetValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFilter(string text)
|
||||
{
|
||||
ToggleAutoSize(false);
|
||||
bool has = text.Length != 0;
|
||||
foreach (DataGridViewRow r in Grid.Rows)
|
||||
{
|
||||
if (!has)
|
||||
{
|
||||
r.Visible = true;
|
||||
continue;
|
||||
}
|
||||
var a = r.Cells[ColumnKey].Value?.ToString() ?? string.Empty;
|
||||
r.Visible = a.Contains(text, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
ToggleAutoSize(true);
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
var rows = Grid.Rows;
|
||||
rows.Clear();
|
||||
|
||||
var count = Storage.Count;
|
||||
rows.Add(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var (a, b) = Storage.GetKey(i);
|
||||
var value = Storage.GetValue(i);
|
||||
|
||||
var row = rows[i];
|
||||
var cells = row.Cells;
|
||||
cells[ColumnIndex].Value = i;
|
||||
cells[ColumnKey].Value = Names.GetName(a);
|
||||
cells[ColumnValue1].Value = ((long)b).ToString();
|
||||
cells[ColumnValue2].Value = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
var rows = Grid.Rows;
|
||||
var count = Math.Min(Storage.Count, rows.Count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var cells = rows[i].Cells;
|
||||
try
|
||||
{
|
||||
var aText = cells[ColumnKey].Value?.ToString() ?? string.Empty;
|
||||
var vText1 = cells[ColumnValue1].Value?.ToString() ?? "0";
|
||||
var vText2 = cells[ColumnValue2].Value?.ToString() ?? "0";
|
||||
ulong a = Names.GetHash(aText);
|
||||
ulong b = long.TryParse(vText1, CultureInfo.InvariantCulture, out var bt) ? (ulong)bt : 0UL;
|
||||
ulong v = ulong.TryParse(vText2, CultureInfo.InvariantCulture, out var vt) ? vt : 0UL;
|
||||
|
||||
Storage.SetValue(i, v);
|
||||
Storage.SetKey(i, a, b);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore invalid row
|
||||
}
|
||||
}
|
||||
Storage.Compress();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grid for EventWorkStorage192 with separate key columns A, B, and C, and one value.
|
||||
/// </summary>
|
||||
public sealed record EventWorkGrid128 : EventWorkGridBase
|
||||
{
|
||||
private readonly EventWorkValueStorageKey128 Storage;
|
||||
private readonly EventWorkLookup Names;
|
||||
|
||||
private const int ColumnIndex = 0;
|
||||
private const int ColumnKeyA = 1;
|
||||
private const int ColumnKeyB = 2;
|
||||
private const int ColumnValue = 3;
|
||||
|
||||
private EventWorkGrid128(EventWorkValueStorageKey128 storage, EventWorkLookup names, SplitContainer container, DataGridView grid, TextBox search)
|
||||
: base(container, grid, search)
|
||||
{
|
||||
Storage = storage;
|
||||
Names = names;
|
||||
|
||||
Grid.CellValidated += ValidateValue;
|
||||
Search.TextChanged += (_, _) => ApplyFilter(Search.Text);
|
||||
ToggleAutoSize(true);
|
||||
}
|
||||
|
||||
public static EventWorkGrid128 CreateValues(TabPage host, EventWorkValueStorageKey128 storage, EventWorkLookup names)
|
||||
{
|
||||
var (container, search) = CreateContainerCommon();
|
||||
host.Controls.Clear();
|
||||
host.Controls.Add(container);
|
||||
|
||||
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||
var dgv = new DoubleBufferedDataGridView
|
||||
{
|
||||
AllowUserToAddRows = false,
|
||||
AllowUserToDeleteRows = false,
|
||||
AllowUserToOrderColumns = true,
|
||||
AllowUserToResizeRows = false,
|
||||
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells,
|
||||
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None,
|
||||
Dock = DockStyle.Fill,
|
||||
RowHeadersVisible = false,
|
||||
Margin = new Padding(0),
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
|
||||
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
|
||||
};
|
||||
|
||||
var c1 = MakeIndexColumn(font);
|
||||
var c2 = MakeKeyColumn(font, "Key A");
|
||||
var c3 = MakeKeyColumn(font, "Key B");
|
||||
var v1 = MakeValueNumberColumn(font, "Value");
|
||||
|
||||
dgv.Columns.AddRange(c1, c2, c3, v1);
|
||||
container.Panel2.Controls.Add(dgv);
|
||||
return new EventWorkGrid128(storage, names, container, dgv, search);
|
||||
}
|
||||
|
||||
private void ToggleAutoSize(bool state)
|
||||
{
|
||||
var columns = Grid.Columns;
|
||||
columns[ColumnIndex].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.ColumnHeader : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnKeyA].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnKeyB].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnValue].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
}
|
||||
|
||||
private void ValidateValue(object? sender, DataGridViewCellEventArgs e)
|
||||
{
|
||||
if (e.ColumnIndex is ColumnValue)
|
||||
{
|
||||
var row = Grid.Rows[e.RowIndex];
|
||||
var cell = row.Cells[e.ColumnIndex];
|
||||
var text = cell.Value?.ToString() ?? string.Empty;
|
||||
if (ulong.TryParse(text, CultureInfo.InvariantCulture, out _))
|
||||
return;
|
||||
WinFormsUtil.Alert("Please enter a valid value.");
|
||||
var i = Convert.ToInt32(row.Cells[ColumnIndex].Value);
|
||||
cell.Value = Storage.GetValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFilter(string text)
|
||||
{
|
||||
ToggleAutoSize(false);
|
||||
bool has = text.Length != 0;
|
||||
foreach (DataGridViewRow r in Grid.Rows)
|
||||
{
|
||||
if (!has)
|
||||
{
|
||||
r.Visible = true;
|
||||
continue;
|
||||
}
|
||||
var a = r.Cells[ColumnKeyA].Value?.ToString() ?? string.Empty;
|
||||
var b = r.Cells[ColumnKeyB].Value?.ToString() ?? string.Empty;
|
||||
r.Visible = a.Contains(text, StringComparison.OrdinalIgnoreCase) ||
|
||||
b.Contains(text, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
ToggleAutoSize(true);
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
var rows = Grid.Rows;
|
||||
rows.Clear();
|
||||
|
||||
var count = Storage.Count;
|
||||
rows.Add(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var (a, b) = Storage.GetKey(i);
|
||||
var value = Storage.GetValue(i);
|
||||
|
||||
var row = rows[i];
|
||||
var cells = row.Cells;
|
||||
cells[ColumnIndex].Value = i;
|
||||
cells[ColumnKeyA].Value = Names.GetName(a);
|
||||
cells[ColumnKeyB].Value = Names.GetName(b);
|
||||
cells[ColumnValue].Value = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
var rows = Grid.Rows;
|
||||
var count = Math.Min(Storage.Count, rows.Count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var cells = rows[i].Cells;
|
||||
try
|
||||
{
|
||||
var aText = cells[ColumnKeyA].Value?.ToString() ?? string.Empty;
|
||||
var bText = cells[ColumnKeyB].Value?.ToString() ?? string.Empty;
|
||||
var vText = cells[ColumnValue].Value?.ToString() ?? "0";
|
||||
ulong a = Names.GetHash(aText);
|
||||
ulong b = Names.GetHash(bText);
|
||||
ulong v = ulong.TryParse(vText, CultureInfo.InvariantCulture, out var vt) ? vt : 0UL;
|
||||
|
||||
Storage.SetValue(i, v);
|
||||
Storage.SetKey(i, a, b);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore invalid row
|
||||
}
|
||||
}
|
||||
Storage.Compress();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grid for EventWorkStorage192 with separate key columns A, B, and C, and one value.
|
||||
/// </summary>
|
||||
public sealed record EventWorkGrid192 : EventWorkGridBase
|
||||
{
|
||||
private readonly EventWorkValueStorageKey192 Storage;
|
||||
private readonly EventWorkLookup Names;
|
||||
|
||||
private const int ColumnIndex = 0;
|
||||
private const int ColumnKeyA = 1;
|
||||
private const int ColumnKeyB = 2;
|
||||
private const int ColumnKeyC = 3;
|
||||
private const int ColumnValue = 4;
|
||||
|
||||
private EventWorkGrid192(EventWorkValueStorageKey192 storage, EventWorkLookup names, SplitContainer container, DataGridView grid, TextBox search)
|
||||
: base(container, grid, search)
|
||||
{
|
||||
Storage = storage;
|
||||
Names = names;
|
||||
|
||||
Grid.CellValidated += ValidateValue;
|
||||
Search.TextChanged += (_, _) => ApplyFilter(Search.Text);
|
||||
ToggleAutoSize(true);
|
||||
}
|
||||
|
||||
public static EventWorkGrid192 CreateValues(TabPage host, EventWorkValueStorageKey192 storage, EventWorkLookup names)
|
||||
{
|
||||
var (container, search) = CreateContainerCommon();
|
||||
host.Controls.Clear();
|
||||
host.Controls.Add(container);
|
||||
|
||||
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||
var dgv = new DoubleBufferedDataGridView
|
||||
{
|
||||
AllowUserToAddRows = false,
|
||||
AllowUserToDeleteRows = false,
|
||||
AllowUserToOrderColumns = true,
|
||||
AllowUserToResizeRows = false,
|
||||
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells,
|
||||
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None,
|
||||
Dock = DockStyle.Fill,
|
||||
RowHeadersVisible = false,
|
||||
Margin = new Padding(0),
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
|
||||
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
|
||||
};
|
||||
|
||||
var c1 = MakeIndexColumn(font);
|
||||
var c2 = MakeKeyColumn(font, "Key A");
|
||||
var c3 = MakeKeyColumn(font, "Key B");
|
||||
var c4 = MakeKeyColumn(font, "Key C");
|
||||
var v1 = MakeValueNumberColumn(font, "Value");
|
||||
|
||||
dgv.Columns.AddRange(c1, c2, c3, c4, v1);
|
||||
container.Panel2.Controls.Add(dgv);
|
||||
return new EventWorkGrid192(storage, names, container, dgv, search);
|
||||
}
|
||||
|
||||
private void ToggleAutoSize(bool state)
|
||||
{
|
||||
var columns = Grid.Columns;
|
||||
columns[ColumnIndex].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.ColumnHeader : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnKeyA].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnKeyB].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnKeyC].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
columns[ColumnValue].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
|
||||
}
|
||||
|
||||
private void ValidateValue(object? sender, DataGridViewCellEventArgs e)
|
||||
{
|
||||
if (e.ColumnIndex is ColumnValue)
|
||||
{
|
||||
var row = Grid.Rows[e.RowIndex];
|
||||
var cell = row.Cells[e.ColumnIndex];
|
||||
var text = cell.Value?.ToString() ?? string.Empty;
|
||||
if (ulong.TryParse(text, CultureInfo.InvariantCulture, out _))
|
||||
return;
|
||||
WinFormsUtil.Alert("Please enter a valid value.");
|
||||
var i = Convert.ToInt32(row.Cells[ColumnIndex].Value);
|
||||
cell.Value = Storage.GetValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFilter(string text)
|
||||
{
|
||||
ToggleAutoSize(false);
|
||||
bool has = text.Length != 0;
|
||||
foreach (DataGridViewRow r in Grid.Rows)
|
||||
{
|
||||
if (!has)
|
||||
{
|
||||
r.Visible = true;
|
||||
continue;
|
||||
}
|
||||
var a = r.Cells[ColumnKeyA].Value?.ToString() ?? string.Empty;
|
||||
var b = r.Cells[ColumnKeyB].Value?.ToString() ?? string.Empty;
|
||||
var c = r.Cells[ColumnKeyC].Value?.ToString() ?? string.Empty;
|
||||
r.Visible = a.Contains(text, StringComparison.OrdinalIgnoreCase) ||
|
||||
b.Contains(text, StringComparison.OrdinalIgnoreCase) ||
|
||||
c.Contains(text, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
ToggleAutoSize(true);
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
var rows = Grid.Rows;
|
||||
rows.Clear();
|
||||
|
||||
var count = Storage.Count;
|
||||
rows.Add(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var (a, b, c) = Storage.GetKey(i);
|
||||
var value = Storage.GetValue(i);
|
||||
|
||||
var row = rows[i];
|
||||
var cells = row.Cells;
|
||||
cells[ColumnIndex].Value = i;
|
||||
cells[ColumnKeyA].Value = Names.GetName(a);
|
||||
cells[ColumnKeyB].Value = Names.GetName(b);
|
||||
cells[ColumnKeyC].Value = Names.GetName(c);
|
||||
cells[ColumnValue].Value = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
var rows = Grid.Rows;
|
||||
var count = Math.Min(Storage.Count, rows.Count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var cells = rows[i].Cells;
|
||||
try
|
||||
{
|
||||
var aText = cells[ColumnKeyA].Value?.ToString() ?? string.Empty;
|
||||
var bText = cells[ColumnKeyB].Value?.ToString() ?? string.Empty;
|
||||
var cText = cells[ColumnKeyC].Value?.ToString() ?? string.Empty;
|
||||
var vText = cells[ColumnValue].Value?.ToString() ?? "0";
|
||||
ulong a = Names.GetHash(aText);
|
||||
ulong b = Names.GetHash(bText);
|
||||
ulong c = Names.GetHash(cText);
|
||||
ulong v = ulong.TryParse(vText, CultureInfo.InvariantCulture, out var vt) ? vt : 0UL;
|
||||
|
||||
Storage.SetValue(i, v);
|
||||
Storage.SetKey(i, a, b, c);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore invalid row
|
||||
}
|
||||
}
|
||||
Storage.Compress();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ private void InitializeComponent()
|
||||
// B_Cancel
|
||||
//
|
||||
B_Cancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
B_Cancel.Location = new System.Drawing.Point(427, 401);
|
||||
B_Cancel.Location = new System.Drawing.Point(507, 401);
|
||||
B_Cancel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
B_Cancel.Name = "B_Cancel";
|
||||
B_Cancel.Size = new System.Drawing.Size(88, 27);
|
||||
@@ -62,7 +62,7 @@ private void InitializeComponent()
|
||||
// B_Save
|
||||
//
|
||||
B_Save.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
B_Save.Location = new System.Drawing.Point(523, 401);
|
||||
B_Save.Location = new System.Drawing.Point(603, 401);
|
||||
B_Save.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
B_Save.Name = "B_Save";
|
||||
B_Save.Size = new System.Drawing.Size(88, 27);
|
||||
@@ -90,7 +90,7 @@ private void InitializeComponent()
|
||||
GB_Research.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
GB_Research.Name = "GB_Research";
|
||||
GB_Research.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
GB_Research.Size = new System.Drawing.Size(598, 351);
|
||||
GB_Research.Size = new System.Drawing.Size(678, 351);
|
||||
GB_Research.TabIndex = 2;
|
||||
GB_Research.Text = "Research";
|
||||
GB_Research.UseVisualStyleBackColor = true;
|
||||
@@ -112,7 +112,7 @@ private void InitializeComponent()
|
||||
// splitContainer1.Panel2
|
||||
//
|
||||
splitContainer1.Panel2.Controls.Add(RTB_Diff);
|
||||
splitContainer1.Size = new System.Drawing.Size(590, 345);
|
||||
splitContainer1.Size = new System.Drawing.Size(670, 345);
|
||||
splitContainer1.SplitterDistance = 60;
|
||||
splitContainer1.TabIndex = 7;
|
||||
//
|
||||
@@ -134,7 +134,7 @@ private void InitializeComponent()
|
||||
TB_NewSAV.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
TB_NewSAV.Name = "TB_NewSAV";
|
||||
TB_NewSAV.ReadOnly = true;
|
||||
TB_NewSAV.Size = new System.Drawing.Size(487, 25);
|
||||
TB_NewSAV.Size = new System.Drawing.Size(567, 25);
|
||||
TB_NewSAV.TabIndex = 5;
|
||||
//
|
||||
// B_LoadNew
|
||||
@@ -155,7 +155,7 @@ private void InitializeComponent()
|
||||
TB_OldSAV.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
TB_OldSAV.Name = "TB_OldSAV";
|
||||
TB_OldSAV.ReadOnly = true;
|
||||
TB_OldSAV.Size = new System.Drawing.Size(487, 25);
|
||||
TB_OldSAV.Size = new System.Drawing.Size(567, 25);
|
||||
TB_OldSAV.TabIndex = 4;
|
||||
//
|
||||
// RTB_Diff
|
||||
@@ -165,7 +165,7 @@ private void InitializeComponent()
|
||||
RTB_Diff.Margin = new System.Windows.Forms.Padding(0);
|
||||
RTB_Diff.Name = "RTB_Diff";
|
||||
RTB_Diff.ReadOnly = true;
|
||||
RTB_Diff.Size = new System.Drawing.Size(590, 281);
|
||||
RTB_Diff.Size = new System.Drawing.Size(670, 281);
|
||||
RTB_Diff.TabIndex = 6;
|
||||
RTB_Diff.Text = "";
|
||||
//
|
||||
@@ -179,14 +179,14 @@ private void InitializeComponent()
|
||||
TC_Features.Name = "TC_Features";
|
||||
TC_Features.Padding = new System.Drawing.Point(0, 0);
|
||||
TC_Features.SelectedIndex = 0;
|
||||
TC_Features.Size = new System.Drawing.Size(606, 381);
|
||||
TC_Features.Size = new System.Drawing.Size(686, 381);
|
||||
TC_Features.TabIndex = 42;
|
||||
//
|
||||
// SAV_FlagWork9a
|
||||
//
|
||||
AllowDrop = true;
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
|
||||
ClientSize = new System.Drawing.Size(624, 441);
|
||||
ClientSize = new System.Drawing.Size(704, 441);
|
||||
Controls.Add(TC_Features);
|
||||
Controls.Add(L_EventFlagWarn);
|
||||
Controls.Add(B_Save);
|
||||
@@ -194,7 +194,6 @@ private void InitializeComponent()
|
||||
Icon = Properties.Resources.Icon;
|
||||
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
MaximizeBox = false;
|
||||
MaximumSize = new System.Drawing.Size(779, 917);
|
||||
MinimizeBox = false;
|
||||
MinimumSize = new System.Drawing.Size(640, 480);
|
||||
Name = "SAV_FlagWork9a";
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace PKHeX.WinForms;
|
||||
|
||||
public sealed partial class SAV_FlagWork9a : Form
|
||||
{
|
||||
private readonly Dictionary<ulong, string> Lookup = [];
|
||||
private readonly Dictionary<ulong, string> Lookup = new() { {FnvHash.HashEmpty, ""} };
|
||||
private readonly EventWorkLookup Names;
|
||||
|
||||
private readonly IEventWorkGrid[] Grids;
|
||||
|
||||
@@ -21,18 +22,28 @@ public SAV_FlagWork9a(SAV9ZA sav)
|
||||
if (File.Exists(path))
|
||||
SCBlockMetadata.AddExtraKeyNames64(Lookup, File.ReadLines(path));
|
||||
|
||||
Names = new EventWorkLookup(Lookup);
|
||||
|
||||
// Create grids for each block
|
||||
Grids =
|
||||
[
|
||||
EventWorkGrid64<bool>.CreateFlags(GetTab(nameof(sav.Blocks.Flags)), sav.Blocks.Flags, Lookup),
|
||||
EventWorkGrid64<bool>.CreateFlags(GetTab(nameof(sav.Blocks.Event)), sav.Blocks.Event, Lookup),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.Work)), sav.Blocks.Work, Lookup),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.Quest)), sav.Blocks.Quest, Lookup),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.WorkMable)), sav.Blocks.WorkMable, Lookup),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.CountMable)), sav.Blocks.CountMable, Lookup),
|
||||
EventWorkGrid64<bool>.CreateFlags(GetTab(nameof(sav.Blocks.Flags)), sav.Blocks.Flags, Names),
|
||||
EventWorkGrid64<bool>.CreateFlags(GetTab(nameof(sav.Blocks.Event)), sav.Blocks.Event, Names),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.Work)), sav.Blocks.Work, Names),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.Quest)), sav.Blocks.Quest, Names),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.WorkMable)), sav.Blocks.WorkMable, Names),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.CountMable)), sav.Blocks.CountMable, Names),
|
||||
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.CountTitle)), sav.Blocks.CountTitle, Lookup),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.WorkSpawn)), sav.Blocks.WorkSpawn, Lookup),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.CountTitle)), sav.Blocks.CountTitle, Names),
|
||||
EventWorkGridTuple.CreateValues(GetTab(nameof(sav.Blocks.Report)), sav.Blocks.Report, Names),
|
||||
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.WorkSpawn)), sav.Blocks.WorkSpawn, Names),
|
||||
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.InfiniteRank)), sav.Blocks.InfiniteRank, Names),
|
||||
EventWorkGridTuple.CreateValues(GetTab(nameof(sav.Blocks.Spawner2)), sav.Blocks.Spawner2, Names),
|
||||
EventWorkGrid128.CreateValues(GetTab(nameof(sav.Blocks.Spawner4)), sav.Blocks.Spawner4, Names),
|
||||
|
||||
EventWorkGridTuple.CreateValues(GetTab(nameof(sav.Blocks.Obstruction)), sav.Blocks.Obstruction, Names),
|
||||
EventWorkGrid192.CreateValues(GetTab(nameof(sav.Blocks.FieldObjectInteractable)), sav.Blocks.FieldObjectInteractable, Names),
|
||||
];
|
||||
|
||||
// Translate headings
|
||||
@@ -142,7 +153,7 @@ private void DiffSaves(string fileUpdated, string filePrevious)
|
||||
break;
|
||||
var u = update.GetValue(i);
|
||||
|
||||
var name = EventWorkGrid64<T2>.GetNameDisplay(hash, Lookup);
|
||||
var name = Names.GetName(hash);
|
||||
if (!previous.TryGetValue(hash, out var p))
|
||||
result.Add($"{name} added with value {u}");
|
||||
else if (!p.Equals(u))
|
||||
@@ -161,7 +172,7 @@ private void DiffSaves(string fileUpdated, string filePrevious)
|
||||
if (hashes.Contains(hash))
|
||||
continue;
|
||||
var p = previous.GetValue(i);
|
||||
var name = EventWorkGrid64<T2>.GetNameDisplay(hash, Lookup);
|
||||
var name = Names.GetName(hash);
|
||||
result.Add($"{name} @ {i:X} removed, was {p}");
|
||||
}
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user