2022-06-26 06:08:28 +00:00
using System ;
2017-09-29 05:20:27 +00:00
using System.Collections.Generic ;
2016-06-20 04:22:43 +00:00
using System.Linq ;
2022-06-18 18:04:24 +00:00
namespace PKHeX.Core ;
/// <summary>
/// Base Class for Save Files
/// </summary>
public abstract class SaveFile : ITrainerInfo , IGameValueLimit , IBoxDetailWallpaper , IBoxDetailName , IGeneration , IVersion
2016-06-20 04:22:43 +00:00
{
2022-06-18 18:04:24 +00:00
// General Object Properties
public byte [ ] Data ;
public SaveFileState State { get ; }
public SaveFileMetadata Metadata { get ; private set ; }
protected SaveFile ( byte [ ] data , bool exportable = true )
2016-06-20 04:22:43 +00:00
{
2022-06-18 18:04:24 +00:00
Data = data ;
State = new SaveFileState ( exportable ) ;
Metadata = new SaveFileMetadata ( this ) ;
}
2020-12-05 13:36:23 +00:00
2022-06-18 18:04:24 +00:00
protected SaveFile ( int size = 0 ) : this ( size = = 0 ? Array . Empty < byte > ( ) : new byte [ size ] , false ) { }
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
protected internal abstract string ShortSummary { get ; }
public abstract string Extension { get ; }
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
protected abstract SaveFile CloneInternal ( ) ;
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
2022-06-18 18:04:24 +00:00
public SaveFile Clone ( )
{
var sav = CloneInternal ( ) ;
2022-09-11 16:59:00 +00:00
sav . Metadata = Metadata with { SAV = sav } ;
2022-06-18 18:04:24 +00:00
return sav ;
}
2020-12-05 13:36:23 +00:00
2022-06-18 18:04:24 +00:00
public virtual string PlayTimeString = > $"{PlayedHours}ː {PlayedMinutes:00}ː {PlayedSeconds:00}" ; // not :
2020-12-05 13:36:23 +00:00
2022-06-18 18:04:24 +00:00
public virtual IReadOnlyList < string > PKMExtensions = > Array . FindAll ( PKM . Extensions , f = >
{
int gen = f [ ^ 1 ] - 0x30 ;
return 3 < = gen & & gen < = Generation ;
} ) ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
// General SAV Properties
public byte [ ] Write ( BinaryExportSetting setting = BinaryExportSetting . None )
{
byte [ ] data = GetFinalData ( ) ;
return Metadata . Finalize ( data , setting ) ;
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
protected virtual byte [ ] GetFinalData ( )
{
SetChecksums ( ) ;
return Data ;
}
2017-06-18 01:37:19 +00:00
2022-06-18 18:04:24 +00:00
#region Metadata & Limits
public virtual string MiscSaveInfo ( ) = > string . Empty ;
public virtual GameVersion Version { get ; protected set ; }
public abstract bool ChecksumsValid { get ; }
public abstract string ChecksumInfo { get ; }
public abstract int Generation { get ; }
public abstract EntityContext Context { get ; }
#endregion
#region Savedata Container Handling
public byte [ ] GetData ( int offset , int length ) = > GetData ( Data , offset , length ) ;
protected static byte [ ] GetData ( byte [ ] data , int offset , int length ) = > data . Slice ( offset , length ) ;
public void SetData ( byte [ ] input , int offset ) = > SetData ( Data , input , offset ) ;
2023-01-22 04:02:33 +00:00
public void SetData ( ReadOnlySpan < byte > input , int offset ) = > SetData ( Data , input , offset ) ;
2022-06-18 18:04:24 +00:00
2023-01-22 04:02:33 +00:00
public void SetData ( Span < byte > dest , ReadOnlySpan < byte > input , int offset )
2022-06-18 18:04:24 +00:00
{
input . CopyTo ( dest [ offset . . ] ) ;
State . Edited = true ;
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
public abstract string GetString ( ReadOnlySpan < byte > data ) ;
public string GetString ( int offset , int length ) = > GetString ( Data . AsSpan ( offset , length ) ) ;
public abstract int SetString ( Span < byte > destBuffer , ReadOnlySpan < char > value , int maxLength , StringConverterOption option ) ;
#endregion
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
public virtual void CopyChangesFrom ( SaveFile sav ) = > SetData ( sav . Data , 0 ) ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
// Offsets
2019-06-20 00:49:50 +00:00
2022-06-18 18:04:24 +00:00
#region Stored PKM Limits
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
public abstract IPersonalTable Personal { get ; }
2022-11-25 01:42:17 +00:00
public abstract int MaxStringLengthOT { get ; }
public abstract int MaxStringLengthNickname { get ; }
2022-08-27 06:43:36 +00:00
public abstract ushort MaxMoveID { get ; }
public abstract ushort MaxSpeciesID { get ; }
2022-06-18 18:04:24 +00:00
public abstract int MaxAbilityID { get ; }
public abstract int MaxItemID { get ; }
public abstract int MaxBallID { get ; }
public abstract int MaxGameID { get ; }
public virtual int MinGameID = > 0 ;
#endregion
/// <summary>
/// Gets the <see cref="bool"/> status of the Flag at the specified offset and index.
/// </summary>
/// <param name="offset">Offset to read from</param>
/// <param name="bitIndex">Bit index to read</param>
/// <returns>Flag is Set (true) or not Set (false)</returns>
public virtual bool GetFlag ( int offset , int bitIndex ) = > FlagUtil . GetFlag ( Data , offset , bitIndex ) ;
2019-06-20 00:49:50 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the <see cref="bool"/> status of the Flag at the specified offset and index.
/// </summary>
/// <param name="offset">Offset to read from</param>
/// <param name="bitIndex">Bit index to read</param>
/// <param name="value">Flag status to set</param>
/// <remarks>Flag is Set (true) or not Set (false)</remarks>
public virtual void SetFlag ( int offset , int bitIndex , bool value ) = > FlagUtil . SetFlag ( Data , offset , bitIndex , value ) ;
public virtual IReadOnlyList < InventoryPouch > Inventory { get = > Array . Empty < InventoryPouch > ( ) ; set { } }
#region Mystery Gift
2022-08-23 06:18:53 +00:00
protected virtual int GiftCountMax = > int . MinValue ;
protected virtual int GiftFlagMax = > 0x800 ;
2022-06-18 18:04:24 +00:00
protected int WondercardData { get ; set ; } = int . MinValue ;
public bool HasWondercards = > WondercardData > - 1 ;
protected virtual bool [ ] MysteryGiftReceivedFlags { get = > Array . Empty < bool > ( ) ; set { } }
protected virtual DataMysteryGift [ ] MysteryGiftCards { get = > Array . Empty < DataMysteryGift > ( ) ; set { } }
public virtual MysteryGiftAlbum GiftAlbum
{
get = > new ( MysteryGiftCards , MysteryGiftReceivedFlags ) ;
set
2019-06-20 00:49:50 +00:00
{
2022-06-18 18:04:24 +00:00
MysteryGiftReceivedFlags = value . Flags ;
MysteryGiftCards = value . Gifts ;
2019-06-20 00:49:50 +00:00
}
2022-06-18 18:04:24 +00:00
}
#endregion
#region Player Info
public virtual int Gender { get ; set ; }
public virtual int Language { get = > - 1 ; set { } }
2022-09-26 14:27:51 +00:00
public virtual int Game { get = > ( int ) GameVersion . Any ; set { } }
2023-01-22 04:02:33 +00:00
public virtual uint ID32 { get ; set ; }
public virtual ushort TID16 { get ; set ; }
public virtual ushort SID16 { get ; set ; }
2022-06-18 18:04:24 +00:00
public virtual string OT { get ; set ; } = "PKHeX" ;
public virtual int PlayedHours { get ; set ; }
public virtual int PlayedMinutes { get ; set ; }
public virtual int PlayedSeconds { get ; set ; }
public virtual uint SecondsToStart { get ; set ; }
public virtual uint SecondsToFame { get ; set ; }
public virtual uint Money { get ; set ; }
public abstract int BoxCount { get ; }
public virtual int SlotCount = > BoxCount * BoxSlotCount ;
public virtual int MaxMoney = > 9999999 ;
public virtual int MaxCoins = > 9999 ;
2023-01-22 04:02:33 +00:00
public TrainerIDFormat TrainerIDDisplayFormat = > this . GetTrainerIDFormat ( ) ;
public uint TrainerTID7 { get = > this . GetTrainerTID7 ( ) ; set = > this . SetTrainerTID7 ( value ) ; }
public uint TrainerSID7 { get = > this . GetTrainerSID7 ( ) ; set = > this . SetTrainerSID7 ( value ) ; }
public uint DisplayTID { get = > this . GetDisplayTID ( ) ; set = > this . SetDisplayTID ( value ) ; }
public uint DisplaySID { get = > this . GetDisplaySID ( ) ; set = > this . SetDisplaySID ( value ) ; }
2019-06-20 00:49:50 +00:00
2022-06-18 18:04:24 +00:00
#endregion
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
#region Party
public virtual int PartyCount { get ; protected set ; }
protected int Party { get ; set ; } = int . MinValue ;
public virtual bool HasParty = > Party > - 1 ;
public abstract int GetPartyOffset ( int slot ) ;
public bool IsPartyAllEggs ( int except = - 1 )
{
if ( ! HasParty )
return false ;
for ( int i = 0 ; i < MaxPartyCount ; i + + )
2017-06-22 03:24:42 +00:00
{
2022-06-18 18:04:24 +00:00
if ( i = = except )
continue ;
if ( IsPartySlotNotEggOrEmpty ( i ) )
2017-06-22 03:24:42 +00:00
return false ;
2022-06-18 18:04:24 +00:00
}
2017-06-22 03:24:42 +00:00
2022-06-18 18:04:24 +00:00
return true ;
}
2021-07-27 06:33:56 +00:00
2022-06-18 18:04:24 +00:00
private bool IsPartySlotNotEggOrEmpty ( int index )
{
var slot = GetPartySlotAtIndex ( index ) ;
return ! slot . IsEgg & & slot . Species ! = 0 ;
}
2021-07-27 06:33:56 +00:00
2022-06-18 18:04:24 +00:00
private const int MaxPartyCount = 6 ;
2021-07-27 06:33:56 +00:00
2022-06-18 18:04:24 +00:00
public IList < PKM > PartyData
{
get
2021-07-27 06:33:56 +00:00
{
2022-06-18 18:04:24 +00:00
var count = PartyCount ;
if ( ( uint ) count > MaxPartyCount )
count = MaxPartyCount ;
2021-04-22 01:33:57 +00:00
2022-06-18 18:04:24 +00:00
PKM [ ] data = new PKM [ count ] ;
for ( int i = 0 ; i < data . Length ; i + + )
data [ i ] = GetPartySlot ( PartyBuffer , GetPartyOffset ( i ) ) ;
return data ;
}
set
2019-09-03 02:30:58 +00:00
{
2022-06-18 18:04:24 +00:00
if ( value . Count is 0 or > MaxPartyCount )
throw new ArgumentOutOfRangeException ( nameof ( value ) , $"Expected 1-6, got {value.Count}" ) ;
2021-03-14 23:16:55 +00:00
#if DEBUG
2022-06-18 18:04:24 +00:00
if ( value [ 0 ] . Species = = 0 )
System . Diagnostics . Debug . WriteLine ( $"Empty first slot, received {value.Count}." ) ;
2021-03-14 23:16:55 +00:00
#endif
2022-06-18 18:04:24 +00:00
int ctr = 0 ;
foreach ( var exist in value . Where ( pk = > pk . Species ! = 0 ) )
SetPartySlot ( exist , PartyBuffer , GetPartyOffset ( ctr + + ) ) ;
2022-10-09 23:15:50 +00:00
PartyCount = ctr ;
2022-06-18 18:04:24 +00:00
for ( int i = ctr ; i < 6 ; i + + )
SetPartySlot ( BlankPKM , PartyBuffer , GetPartyOffset ( i ) ) ;
2017-06-22 03:24:42 +00:00
}
2022-06-18 18:04:24 +00:00
}
#endregion
// Varied Methods
protected abstract void SetChecksums ( ) ;
#region Daycare
public bool HasDaycare = > DaycareOffset > - 1 ;
protected int DaycareOffset { get ; set ; } = int . MinValue ;
2022-08-23 06:18:53 +00:00
public virtual int DaycareSeedSize = > 0 ;
public int DaycareIndex ;
2022-06-18 18:04:24 +00:00
public virtual bool HasTwoDaycares = > false ;
public virtual int GetDaycareSlotOffset ( int loc , int slot ) = > - 1 ;
public virtual uint? GetDaycareEXP ( int loc , int slot ) = > null ;
public virtual string GetDaycareRNGSeed ( int loc ) = > string . Empty ;
public virtual bool? IsDaycareHasEgg ( int loc ) = > null ;
public virtual bool? IsDaycareOccupied ( int loc , int slot ) = > null ;
public virtual void SetDaycareEXP ( int loc , int slot , uint EXP ) { }
public virtual void SetDaycareRNGSeed ( int loc , string seed ) { }
public virtual void SetDaycareHasEgg ( int loc , bool hasEgg ) { }
public virtual void SetDaycareOccupied ( int loc , int slot , bool occupied ) { }
#endregion
public PKM GetPartySlotAtIndex ( int index ) = > GetPartySlot ( PartyBuffer , GetPartyOffset ( index ) ) ;
public void SetPartySlotAtIndex ( PKM pk , int index , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
{
// update party count
if ( ( uint ) index > 5 )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
2017-01-07 05:22:42 +00:00
2022-06-18 18:04:24 +00:00
int currentCount = PartyCount ;
if ( pk . Species ! = 0 )
{
if ( currentCount < = index )
PartyCount = index + 1 ;
2019-11-16 01:34:18 +00:00
}
2022-06-18 18:04:24 +00:00
else if ( currentCount > index )
2019-11-16 01:34:18 +00:00
{
2022-06-18 18:04:24 +00:00
PartyCount = index ;
2019-09-03 02:30:58 +00:00
}
2018-05-12 15:13:39 +00:00
2022-06-18 18:04:24 +00:00
int offset = GetPartyOffset ( index ) ;
SetPartySlot ( pk , PartyBuffer , offset , trade , dex ) ;
}
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
public void SetSlotFormatParty ( PKM pk , byte [ ] data , int offset , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
{
if ( pk . GetType ( ) ! = PKMType )
throw new ArgumentException ( $"PKM Format needs to be {PKMType} when setting to this Save File." ) ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
UpdatePKM ( pk , isParty : true , trade , dex ) ;
SetPartyValues ( pk , isParty : true ) ;
WritePartySlot ( pk , data , offset ) ;
}
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
public void SetPartySlot ( PKM pk , byte [ ] data , int offset , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
{
if ( pk . GetType ( ) ! = PKMType )
throw new ArgumentException ( $"PKM Format needs to be {PKMType} when setting to this Save File." ) ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
UpdatePKM ( pk , isParty : true , trade , dex ) ;
SetPartyValues ( pk , isParty : true ) ;
WritePartySlot ( pk , data , offset ) ;
}
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
public void SetSlotFormatStored ( PKM pk , Span < byte > data , int offset , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
{
if ( pk . GetType ( ) ! = PKMType )
throw new ArgumentException ( $"PKM Format needs to be {PKMType} when setting to this Save File." ) ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
UpdatePKM ( pk , isParty : false , trade , dex ) ;
SetPartyValues ( pk , isParty : false ) ;
WriteSlotFormatStored ( pk , data , offset ) ;
}
2018-11-11 05:04:24 +00:00
2022-06-18 18:04:24 +00:00
public void SetBoxSlot ( PKM pk , Span < byte > data , int offset , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
{
if ( pk . GetType ( ) ! = PKMType )
throw new ArgumentException ( $"PKM Format needs to be {PKMType} when setting to this Save File." ) ;
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
UpdatePKM ( pk , isParty : false , trade , dex ) ;
SetPartyValues ( pk , isParty : false ) ;
WriteBoxSlot ( pk , data , offset ) ;
}
2020-10-31 05:44:08 +00:00
2022-06-18 18:04:24 +00:00
public void DeletePartySlot ( int slot )
{
int newEmpty = PartyCount - 1 ;
if ( ( uint ) slot > newEmpty ) // beyond party range (or empty data already present)
return ;
// Move all party slots down one
for ( int i = slot + 1 ; i < = newEmpty ; i + + ) // Slide slots down
2020-10-31 05:44:08 +00:00
{
2022-06-18 18:04:24 +00:00
var current = GetPartySlotAtIndex ( i ) ;
SetPartySlotAtIndex ( current , i - 1 , PKMImportSetting . Skip , PKMImportSetting . Skip ) ;
2019-09-03 02:30:58 +00:00
}
2022-06-18 18:04:24 +00:00
SetPartySlotAtIndex ( BlankPKM , newEmpty , PKMImportSetting . Skip , PKMImportSetting . Skip ) ;
// PartyCount will automatically update via above call. Do not adjust.
}
2017-01-07 05:22:42 +00:00
2022-06-18 18:04:24 +00:00
#region Slot Storing
public static PKMImportSetting SetUpdateDex { protected get ; set ; } = PKMImportSetting . Update ;
public static PKMImportSetting SetUpdatePKM { protected get ; set ; } = PKMImportSetting . Update ;
public abstract Type PKMType { get ; }
protected abstract PKM GetPKM ( byte [ ] data ) ;
protected abstract byte [ ] DecryptPKM ( byte [ ] data ) ;
public abstract PKM BlankPKM { get ; }
protected abstract int SIZE_STORED { get ; }
protected abstract int SIZE_PARTY { get ; }
public virtual int SIZE_BOXSLOT = > SIZE_STORED ;
public abstract int MaxEV { get ; }
public virtual int MaxIV = > 31 ;
public abstract IReadOnlyList < ushort > HeldItems { get ; }
protected virtual byte [ ] BoxBuffer = > Data ;
protected virtual byte [ ] PartyBuffer = > Data ;
public virtual bool IsPKMPresent ( ReadOnlySpan < byte > data ) = > EntityDetection . IsPresent ( data ) ;
public virtual PKM GetDecryptedPKM ( byte [ ] data ) = > GetPKM ( DecryptPKM ( data ) ) ;
public virtual PKM GetPartySlot ( byte [ ] data , int offset ) = > GetDecryptedPKM ( GetData ( data , offset , SIZE_PARTY ) ) ;
public virtual PKM GetStoredSlot ( byte [ ] data , int offset ) = > GetDecryptedPKM ( GetData ( data , offset , SIZE_STORED ) ) ;
public virtual PKM GetBoxSlot ( int offset ) = > GetStoredSlot ( BoxBuffer , offset ) ;
public virtual byte [ ] GetDataForFormatStored ( PKM pk ) = > pk . EncryptedBoxData ;
public virtual byte [ ] GetDataForFormatParty ( PKM pk ) = > pk . EncryptedPartyData ;
public virtual byte [ ] GetDataForParty ( PKM pk ) = > pk . EncryptedPartyData ;
public virtual byte [ ] GetDataForBox ( PKM pk ) = > pk . EncryptedBoxData ;
public virtual void WriteSlotFormatStored ( PKM pk , Span < byte > data , int offset ) = > SetData ( data , GetDataForFormatStored ( pk ) , offset ) ;
public virtual void WriteSlotFormatParty ( PKM pk , Span < byte > data , int offset ) = > SetData ( data , GetDataForFormatParty ( pk ) , offset ) ;
public virtual void WritePartySlot ( PKM pk , Span < byte > data , int offset ) = > SetData ( data , GetDataForParty ( pk ) , offset ) ;
public virtual void WriteBoxSlot ( PKM pk , Span < byte > data , int offset ) = > SetData ( data , GetDataForBox ( pk ) , offset ) ;
protected virtual void SetPartyValues ( PKM pk , bool isParty )
{
if ( ! isParty )
return ;
if ( pk . PartyStatsPresent ) // Stats already present
return ;
pk . ResetPartyStats ( ) ;
}
2017-01-07 05:22:42 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Conditions a <see cref="pk"/> for this save file as if it was traded to it.
/// </summary>
/// <param name="pk">Entity to adapt</param>
/// <param name="party">Entity exists in party format</param>
/// <param name="trade">Setting on whether or not to adapt</param>
public void AdaptPKM ( PKM pk , bool party = true , PKMImportSetting trade = PKMImportSetting . UseDefault )
{
if ( GetTradeUpdateSetting ( trade ) )
SetPKM ( pk , party ) ;
}
2017-01-07 05:22:42 +00:00
2022-06-18 18:04:24 +00:00
protected void UpdatePKM ( PKM pk , bool isParty , PKMImportSetting trade , PKMImportSetting dex )
{
AdaptPKM ( pk , isParty , trade ) ;
if ( GetDexUpdateSetting ( dex ) )
SetDex ( pk ) ;
}
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
private static bool GetTradeUpdateSetting ( PKMImportSetting trade = PKMImportSetting . UseDefault )
{
if ( trade = = PKMImportSetting . UseDefault )
trade = SetUpdatePKM ;
return trade = = PKMImportSetting . Update ;
}
private static bool GetDexUpdateSetting ( PKMImportSetting trade = PKMImportSetting . UseDefault )
{
if ( trade = = PKMImportSetting . UseDefault )
trade = SetUpdateDex ;
return trade = = PKMImportSetting . Update ;
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
protected virtual void SetPKM ( PKM pk , bool isParty = false ) { }
protected virtual void SetDex ( PKM pk ) { }
#endregion
#region Pokédex
public int PokeDex { get ; protected set ; } = int . MinValue ;
public bool HasPokeDex = > PokeDex > - 1 ;
2022-08-27 06:43:36 +00:00
public virtual bool GetSeen ( ushort species ) = > false ;
public virtual void SetSeen ( ushort species , bool seen ) { }
public virtual bool GetCaught ( ushort species ) = > false ;
public virtual void SetCaught ( ushort species , bool caught ) { }
public int SeenCount
{
get
{
int ctr = 0 ;
for ( ushort i = 1 ; i < = MaxSpeciesID ; i + + )
{
if ( GetSeen ( i ) )
ctr + + ;
}
return ctr ;
}
}
/// <summary> Count of unique Species Caught (Owned) </summary>
public int CaughtCount
{
get
{
int ctr = 0 ;
for ( ushort i = 1 ; i < = MaxSpeciesID ; i + + )
{
if ( GetCaught ( i ) )
ctr + + ;
}
return ctr ;
}
}
2022-06-18 18:04:24 +00:00
public decimal PercentSeen = > ( decimal ) SeenCount / MaxSpeciesID ;
public decimal PercentCaught = > ( decimal ) CaughtCount / MaxSpeciesID ;
#endregion
public bool HasBox = > Box > - 1 ;
public virtual int BoxSlotCount = > 30 ;
public virtual int BoxesUnlocked { get = > - 1 ; set { } }
public virtual byte [ ] BoxFlags { get = > Array . Empty < byte > ( ) ; set { } }
public virtual int CurrentBox { get ; set ; }
#region BoxData
protected int Box { get ; set ; } = int . MinValue ;
public IList < PKM > BoxData
{
get
2017-10-18 06:19:34 +00:00
{
2022-06-18 18:04:24 +00:00
PKM [ ] data = new PKM [ BoxCount * BoxSlotCount ] ;
for ( int box = 0 ; box < BoxCount ; box + + )
AddBoxData ( data , box , box * BoxSlotCount ) ;
return data ;
}
set
{
if ( value . Count ! = BoxCount * BoxSlotCount )
throw new ArgumentException ( $"Expected {BoxCount * BoxSlotCount}, got {value.Count}" ) ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
for ( int b = 0 ; b < BoxCount ; b + + )
SetBoxData ( value , b , b * BoxSlotCount ) ;
2017-10-18 06:19:34 +00:00
}
2022-06-18 18:04:24 +00:00
}
2016-10-29 18:32:21 +00:00
2022-06-18 18:04:24 +00:00
public int SetBoxData ( IList < PKM > value , int box , int index = 0 )
{
int skipped = 0 ;
for ( int slot = 0 ; slot < BoxSlotCount ; slot + + )
2019-09-03 02:30:58 +00:00
{
2022-06-18 18:04:24 +00:00
var flags = GetSlotFlags ( box , slot ) ;
if ( ! flags . IsOverwriteProtected ( ) )
SetBoxSlotAtIndex ( value [ index + slot ] , box , slot ) ;
else
+ + skipped ;
2019-09-03 02:30:58 +00:00
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
return skipped ;
}
public PKM [ ] GetBoxData ( int box )
{
var data = new PKM [ BoxSlotCount ] ;
AddBoxData ( data , box , 0 ) ;
return data ;
}
public void AddBoxData ( IList < PKM > data , int box , int index )
{
for ( int slot = 0 ; slot < BoxSlotCount ; slot + + )
2016-10-29 18:32:21 +00:00
{
2022-06-18 18:04:24 +00:00
int i = slot + index ;
data [ i ] = GetBoxSlotAtIndex ( box , slot ) ;
2016-10-29 18:32:21 +00:00
}
2022-06-18 18:04:24 +00:00
}
#endregion
#region Storage Health & Metadata
protected int [ ] TeamSlots = Array . Empty < int > ( ) ;
/// <summary>
/// Slot indexes that are protected from overwriting.
/// </summary>
protected virtual IList < int > [ ] SlotPointers = > new [ ] { TeamSlots } ;
public virtual StorageSlotSource GetSlotFlags ( int index ) = > StorageSlotSource . None ;
public StorageSlotSource GetSlotFlags ( int box , int slot ) = > GetSlotFlags ( ( box * BoxSlotCount ) + slot ) ;
2023-01-22 04:02:33 +00:00
public bool IsSlotLocked ( int box , int slot ) = > GetSlotFlags ( box , slot ) . HasFlag ( StorageSlotSource . Locked ) ;
public bool IsSlotLocked ( int index ) = > GetSlotFlags ( index ) . HasFlag ( StorageSlotSource . Locked ) ;
2022-06-18 18:04:24 +00:00
public bool IsSlotOverwriteProtected ( int box , int slot ) = > GetSlotFlags ( box , slot ) . IsOverwriteProtected ( ) ;
public bool IsSlotOverwriteProtected ( int index ) = > GetSlotFlags ( index ) . IsOverwriteProtected ( ) ;
private const int StorageFullValue = - 1 ;
public bool IsStorageFull = > NextOpenBoxSlot ( ) = = StorageFullValue ;
public int NextOpenBoxSlot ( int lastKnownOccupied = - 1 )
{
var storage = BoxBuffer . AsSpan ( ) ;
2022-10-02 20:14:42 +00:00
int count = SlotCount ;
2022-06-18 18:04:24 +00:00
for ( int i = lastKnownOccupied + 1 ; i < count ; i + + )
2016-10-29 18:32:21 +00:00
{
2022-06-18 18:04:24 +00:00
int offset = GetBoxSlotOffset ( i ) ;
if ( ! IsPKMPresent ( storage [ offset . . ] ) )
return i ;
2019-09-03 02:30:58 +00:00
}
2022-06-18 18:04:24 +00:00
return StorageFullValue ;
}
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
protected virtual bool IsSlotSwapProtected ( int box , int slot ) = > false ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
private bool IsRegionOverwriteProtected ( int min , int max )
{
foreach ( var arrays in SlotPointers )
2019-09-03 02:30:58 +00:00
{
2022-06-18 18:04:24 +00:00
foreach ( int slotIndex in arrays )
2021-07-27 06:33:56 +00:00
{
2022-06-18 18:04:24 +00:00
if ( ! GetSlotFlags ( slotIndex ) . IsOverwriteProtected ( ) )
continue ;
if ( ArrayUtil . WithinRange ( slotIndex , min , max ) )
return true ;
2021-07-27 06:33:56 +00:00
}
2019-09-03 02:30:58 +00:00
}
2022-06-18 18:04:24 +00:00
return false ;
}
public bool IsAnySlotLockedInBox ( int BoxStart , int BoxEnd )
{
foreach ( var arrays in SlotPointers )
2019-09-03 02:30:58 +00:00
{
2022-06-18 18:04:24 +00:00
foreach ( int slotIndex in arrays )
2021-07-27 06:33:56 +00:00
{
2023-01-22 04:02:33 +00:00
if ( ! GetSlotFlags ( slotIndex ) . HasFlag ( StorageSlotSource . Locked ) )
2022-06-18 18:04:24 +00:00
continue ;
if ( ArrayUtil . WithinRange ( slotIndex , BoxStart * BoxSlotCount , ( BoxEnd + 1 ) * BoxSlotCount ) )
return true ;
2021-07-27 06:33:56 +00:00
}
2016-10-29 18:32:21 +00:00
}
2022-06-18 18:04:24 +00:00
return false ;
}
#endregion
2016-09-19 05:47:31 +00:00
2022-06-18 18:04:24 +00:00
#region Storage Offsets and Indexing
public abstract int GetBoxOffset ( int box ) ;
public int GetBoxSlotOffset ( int box , int slot ) = > GetBoxOffset ( box ) + ( slot * SIZE_BOXSLOT ) ;
public PKM GetBoxSlotAtIndex ( int box , int slot ) = > GetBoxSlot ( GetBoxSlotOffset ( box , slot ) ) ;
2018-05-20 03:48:03 +00:00
2022-06-18 18:04:24 +00:00
public void GetBoxSlotFromIndex ( int index , out int box , out int slot )
{
box = index / BoxSlotCount ;
if ( ( uint ) box > = BoxCount )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
slot = index % BoxSlotCount ;
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
public PKM GetBoxSlotAtIndex ( int index )
{
GetBoxSlotFromIndex ( index , out int box , out int slot ) ;
return GetBoxSlotAtIndex ( box , slot ) ;
}
2018-05-20 03:48:03 +00:00
2022-06-18 18:04:24 +00:00
public int GetBoxSlotOffset ( int index )
{
GetBoxSlotFromIndex ( index , out int box , out int slot ) ;
return GetBoxSlotOffset ( box , slot ) ;
}
2019-03-30 00:52:26 +00:00
2022-06-18 18:04:24 +00:00
public void SetBoxSlotAtIndex ( PKM pk , int box , int slot , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
= > SetBoxSlot ( pk , BoxBuffer , GetBoxSlotOffset ( box , slot ) , trade , dex ) ;
2019-03-30 00:52:26 +00:00
2022-06-18 18:04:24 +00:00
public void SetBoxSlotAtIndex ( PKM pk , int index , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
= > SetBoxSlot ( pk , BoxBuffer , GetBoxSlotOffset ( index ) , trade , dex ) ;
#endregion
2019-09-13 02:01:06 +00:00
2022-06-18 18:04:24 +00:00
#region Storage Manipulations
2018-05-20 03:48:03 +00:00
2022-06-18 18:04:24 +00:00
public bool MoveBox ( int box , int insertBeforeBox )
{
if ( box = = insertBeforeBox ) // no movement required
2019-09-03 02:30:58 +00:00
return true ;
2022-06-18 18:04:24 +00:00
if ( ( uint ) box > = BoxCount | | ( uint ) insertBeforeBox > = BoxCount ) // invalid box positions
return false ;
MoveBox ( box , insertBeforeBox , BoxBuffer ) ;
return true ;
}
private void MoveBox ( int box , int insertBeforeBox , byte [ ] storage )
{
int pos1 = BoxSlotCount * box ;
int pos2 = BoxSlotCount * insertBeforeBox ;
int min = Math . Min ( pos1 , pos2 ) ;
int max = Math . Max ( pos1 , pos2 ) ;
int len = BoxSlotCount * SIZE_BOXSLOT ;
byte [ ] boxdata = storage . Slice ( GetBoxOffset ( 0 ) , len * BoxCount ) ; // get all boxes
string [ ] boxNames = Get ( GetBoxName , BoxCount ) ;
int [ ] boxWallpapers = Get ( GetBoxWallpaper , BoxCount ) ;
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
static T [ ] Get < T > ( Func < int , T > act , int count )
2016-06-20 04:22:43 +00:00
{
2022-06-18 18:04:24 +00:00
T [ ] result = new T [ count ] ;
for ( int i = 0 ; i < result . Length ; i + + )
result [ i ] = act ( i ) ;
return result ;
}
2016-06-20 04:22:43 +00:00
2022-06-18 18:04:24 +00:00
min / = BoxSlotCount ;
max / = BoxSlotCount ;
2021-05-08 05:11:10 +00:00
2022-06-18 18:04:24 +00:00
// move all boxes within range to final spot
for ( int i = min , ctr = min ; i < max ; i + + )
{
int b = insertBeforeBox ; // if box is the moved box, move to insertion point, else move to unused box.
if ( i ! = box )
2021-05-08 05:11:10 +00:00
{
2022-06-18 18:04:24 +00:00
if ( insertBeforeBox = = ctr )
+ + ctr ;
b = ctr + + ;
2021-05-08 05:11:10 +00:00
}
2018-05-12 15:13:39 +00:00
2022-06-18 18:04:24 +00:00
Buffer . BlockCopy ( boxdata , len * i , storage , GetBoxOffset ( b ) , len ) ;
SetBoxName ( b , boxNames [ i ] ) ;
SetBoxWallpaper ( b , boxWallpapers [ i ] ) ;
}
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
SlotPointerUtil . UpdateMove ( box , insertBeforeBox , BoxSlotCount , SlotPointers ) ;
}
2016-07-05 03:04:43 +00:00
2022-06-18 18:04:24 +00:00
public bool SwapBox ( int box1 , int box2 )
{
if ( box1 = = box2 ) // no movement required
return true ;
if ( ( uint ) box1 > = BoxCount | | ( uint ) box2 > = BoxCount ) // invalid box positions
return false ;
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
if ( ! IsBoxAbleToMove ( box1 ) | | ! IsBoxAbleToMove ( box2 ) )
return false ;
2019-03-30 00:52:26 +00:00
2022-06-18 18:04:24 +00:00
SwapBox ( box1 , box2 , BoxBuffer ) ;
return true ;
}
2019-03-30 00:52:26 +00:00
2022-06-18 18:04:24 +00:00
private void SwapBox ( int box1 , int box2 , Span < byte > boxData )
{
int b1o = GetBoxOffset ( box1 ) ;
int b2o = GetBoxOffset ( box2 ) ;
int len = BoxSlotCount * SIZE_BOXSLOT ;
Span < byte > b1 = stackalloc byte [ len ] ;
boxData . Slice ( b1o , len ) . CopyTo ( b1 ) ;
boxData . Slice ( b2o , len ) . CopyTo ( boxData [ b1o . . ] ) ;
b1 . CopyTo ( boxData [ b2o . . ] ) ;
// Name
string b1n = GetBoxName ( box1 ) ;
SetBoxName ( box1 , GetBoxName ( box2 ) ) ;
SetBoxName ( box2 , b1n ) ;
// Wallpaper
int b1w = GetBoxWallpaper ( box1 ) ;
SetBoxWallpaper ( box1 , GetBoxWallpaper ( box2 ) ) ;
SetBoxWallpaper ( box2 , b1w ) ;
// Pointers
SlotPointerUtil . UpdateSwap ( box1 , box2 , BoxSlotCount , SlotPointers ) ;
}
2019-03-30 00:52:26 +00:00
2022-06-18 18:04:24 +00:00
private bool IsBoxAbleToMove ( int box )
{
int min = BoxSlotCount * box ;
int max = min + BoxSlotCount ;
return ! IsRegionOverwriteProtected ( min , max ) ;
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sorts all <see cref="PKM"/> present within the range specified by <see cref="BoxStart"/> and <see cref="BoxEnd"/> with the provied <see cref="sortMethod"/>.
/// </summary>
/// <param name="BoxStart">Starting box; if not provided, will iterate from the first box.</param>
/// <param name="BoxEnd">Ending box; if not provided, will iterate to the end.</param>
/// <param name="sortMethod">Sorting logic required to order a <see cref="PKM"/> with respect to its peers; if not provided, will use a default sorting method.</param>
/// <param name="reverse">Reverse the sorting order</param>
/// <returns>Count of repositioned <see cref="PKM"/> slots.</returns>
public int SortBoxes ( int BoxStart = 0 , int BoxEnd = - 1 , Func < IEnumerable < PKM > , int , IEnumerable < PKM > > ? sortMethod = null , bool reverse = false )
{
var BD = BoxData ;
int start = BoxSlotCount * BoxStart ;
var Section = BD . Skip ( start ) ;
if ( BoxEnd > = BoxStart )
Section = Section . Take ( BoxSlotCount * ( BoxEnd - BoxStart + 1 ) ) ;
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
Func < int , bool > skip = IsSlotOverwriteProtected ;
Section = Section . Where ( ( _ , i ) = > ! skip ( start + i ) ) ;
var method = sortMethod ? ? ( ( z , _ ) = > z . OrderBySpecies ( ) ) ;
var Sorted = method ( Section , start ) ;
if ( reverse )
Sorted = Sorted . ReverseSort ( ) ;
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
var result = Sorted . ToArray ( ) ;
var boxclone = new PKM [ BD . Count ] ;
BD . CopyTo ( boxclone , 0 ) ;
int count = result . CopyTo ( boxclone , skip , start ) ;
SlotPointerUtil . UpdateRepointFrom ( boxclone , BD , 0 , SlotPointers ) ;
for ( int i = 0 ; i < boxclone . Length ; i + + )
2016-06-20 04:22:43 +00:00
{
2022-06-18 18:04:24 +00:00
var pk = boxclone [ i ] ;
SetBoxSlotAtIndex ( pk , i , PKMImportSetting . Skip , PKMImportSetting . Skip ) ;
2016-06-20 04:22:43 +00:00
}
2022-06-18 18:04:24 +00:00
return count ;
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Compresses the <see cref="BoxData"/> by pulling out the empty storage slots and putting them at the end, retaining all existing data.
/// </summary>
/// <param name="storedCount">Count of actual <see cref="PKM"/> stored.</param>
2022-06-26 06:08:28 +00:00
/// <param name="slotPointers">Important slot pointers that need to be re-pointed if a slot moves.</param>
2022-06-18 18:04:24 +00:00
/// <returns>True if <see cref="BoxData"/> was updated, false if no update done.</returns>
2022-06-26 06:08:28 +00:00
public bool CompressStorage ( out int storedCount , Span < int > slotPointers ) = > this . CompressStorage ( BoxBuffer , out storedCount , slotPointers ) ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
2022-06-26 06:08:28 +00:00
/// Removes all <see cref="PKM"/> present within the range specified by <see cref="BoxStart"/> and <see cref="BoxEnd"/> if the provided <see cref="deleteCriteria"/> is satisfied.
2022-06-18 18:04:24 +00:00
/// </summary>
/// <param name="BoxStart">Starting box; if not provided, will iterate from the first box.</param>
/// <param name="BoxEnd">Ending box; if not provided, will iterate to the end.</param>
/// <param name="deleteCriteria">Criteria required to be satisfied for a <see cref="PKM"/> to be deleted; if not provided, will clear if possible.</param>
/// <returns>Count of deleted <see cref="PKM"/> slots.</returns>
public int ClearBoxes ( int BoxStart = 0 , int BoxEnd = - 1 , Func < PKM , bool > ? deleteCriteria = null )
{
var storage = BoxBuffer . AsSpan ( ) ;
2022-08-05 18:24:54 +00:00
if ( ( uint ) BoxEnd > = BoxCount )
2022-06-18 18:04:24 +00:00
BoxEnd = BoxCount - 1 ;
2017-03-04 17:38:39 +00:00
2022-06-18 18:04:24 +00:00
var blank = GetDataForBox ( BlankPKM ) ;
int deleted = 0 ;
for ( int i = BoxStart ; i < = BoxEnd ; i + + )
{
for ( int p = 0 ; p < BoxSlotCount ; p + + )
2016-06-20 04:22:43 +00:00
{
2022-06-18 18:04:24 +00:00
if ( IsSlotOverwriteProtected ( i , p ) )
continue ;
var ofs = GetBoxSlotOffset ( i , p ) ;
if ( ! IsPKMPresent ( storage [ ofs . . ] ) )
continue ;
if ( deleteCriteria ! = null )
2018-05-01 00:29:13 +00:00
{
2022-06-18 18:04:24 +00:00
var pk = GetBoxSlotAtIndex ( i , p ) ;
if ( ! deleteCriteria ( pk ) )
2019-02-21 01:59:54 +00:00
continue ;
2018-05-01 00:29:13 +00:00
}
2022-06-18 18:04:24 +00:00
SetData ( storage , blank , ofs ) ;
+ + deleted ;
2016-06-20 04:22:43 +00:00
}
}
2022-06-18 18:04:24 +00:00
return deleted ;
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Modifies all <see cref="PKM"/> present within the range specified by <see cref="BoxStart"/> and <see cref="BoxEnd"/> with the modification routine provided by <see cref="action"/>.
/// </summary>
/// <param name="action">Modification to perform on a <see cref="PKM"/></param>
/// <param name="BoxStart">Starting box; if not provided, will iterate from the first box.</param>
/// <param name="BoxEnd">Ending box (inclusive); if not provided, will iterate to the end.</param>
/// <returns>Count of modified <see cref="PKM"/> slots.</returns>
public int ModifyBoxes ( Action < PKM > action , int BoxStart = 0 , int BoxEnd = - 1 )
{
2022-08-05 18:24:54 +00:00
if ( ( uint ) BoxEnd > = BoxCount )
2022-06-18 18:04:24 +00:00
BoxEnd = BoxCount - 1 ;
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
2022-06-18 18:04:24 +00:00
var storage = BoxBuffer . AsSpan ( ) ;
int modified = 0 ;
for ( int b = BoxStart ; b < = BoxEnd ; b + + )
{
for ( int s = 0 ; s < BoxSlotCount ; s + + )
2018-04-22 19:43:18 +00:00
{
2022-06-18 18:04:24 +00:00
if ( IsSlotOverwriteProtected ( b , s ) )
continue ;
var ofs = GetBoxSlotOffset ( b , s ) ;
if ( ! IsPKMPresent ( storage [ ofs . . ] ) )
continue ;
var pk = GetBoxSlotAtIndex ( b , s ) ;
action ( pk ) ;
+ + modified ;
SetBoxSlot ( pk , storage , ofs , PKMImportSetting . Skip , PKMImportSetting . Skip ) ;
2018-04-22 19:43:18 +00:00
}
}
2022-06-18 18:04:24 +00:00
return modified ;
}
#endregion
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
#region Storage Name & Decoration
public virtual bool HasBoxWallpapers = > GetBoxWallpaperOffset ( 0 ) > - 1 ;
public virtual bool HasNamableBoxes = > HasBoxWallpapers ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
public abstract string GetBoxName ( int box ) ;
2023-01-22 04:02:33 +00:00
public abstract void SetBoxName ( int box , ReadOnlySpan < char > value ) ;
2022-06-18 18:04:24 +00:00
protected virtual int GetBoxWallpaperOffset ( int box ) = > - 1 ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
public virtual int GetBoxWallpaper ( int box )
{
int offset = GetBoxWallpaperOffset ( box ) ;
if ( offset < 0 | | ( uint ) box > BoxCount )
return box ;
return Data [ offset ] ;
}
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
public virtual void SetBoxWallpaper ( int box , int value )
{
int offset = GetBoxWallpaperOffset ( box ) ;
if ( offset < 0 | | ( uint ) box > BoxCount )
return ;
Data [ offset ] = ( byte ) value ;
}
#endregion
2016-06-20 04:22:43 +00:00
2022-06-18 18:04:24 +00:00
#region Box Binaries
public byte [ ] GetPCBinary ( ) = > BoxData . SelectMany ( GetDataForBox ) . ToArray ( ) ;
public byte [ ] GetBoxBinary ( int box ) = > GetBoxData ( box ) . SelectMany ( GetDataForBox ) . ToArray ( ) ;
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
public bool SetPCBinary ( byte [ ] data )
{
if ( IsRegionOverwriteProtected ( 0 , SlotCount ) )
return false ;
2016-06-20 04:22:43 +00:00
2022-06-18 18:04:24 +00:00
int expectLength = SlotCount * SIZE_BOXSLOT ;
return SetConcatenatedBinary ( data , expectLength ) ;
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
public bool SetBoxBinary ( byte [ ] data , int box )
{
int start = box * BoxSlotCount ;
int end = start + BoxSlotCount ;
2018-12-05 06:59:28 +00:00
2022-06-18 18:04:24 +00:00
if ( IsRegionOverwriteProtected ( start , end ) )
return false ;
2016-06-20 04:22:43 +00:00
2022-06-18 18:04:24 +00:00
int expectLength = BoxSlotCount * SIZE_BOXSLOT ;
return SetConcatenatedBinary ( data , expectLength , start ) ;
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
private bool SetConcatenatedBinary ( byte [ ] data , int expectLength , int start = 0 )
{
if ( data . Length ! = expectLength )
return false ;
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
var BD = BoxData ;
var entryLength = SIZE_BOXSLOT ;
var pkdata = ArrayUtil . EnumerateSplit ( data , entryLength ) ;
Track a PKM's Box,Slot,StorageFlags,Identifier metadata separately (#3222)
* Track a PKM's Box,Slot,StorageFlags,Identifier metadata separately
Don't store within the object, track the slot origin data separately.
Batch editing now pre-filters if using Box/Slot/Identifier logic; split up mods/filters as they're starting to get pretty hefty.
- Requesting a Box Data report now shows all slots in the save file (party, misc)
- Can now exclude backup saves from database search via toggle (separate from settings preventing load entirely)
- Replace some linq usages with direct code
* Remove WasLink virtual in PKM
Inline any logic, since we now have encounter objects to indicate matching, rather than the proto-legality logic checking properties of a PKM.
* Use Fateful to directly check gen5 mysterygift origins
No other encounter types in gen5 apply Fateful
* Simplify double ball comparison
Used to be separate for deferral cases, now no longer needed to be separate.
* Grab move/relearn reference and update locally
Fix relearn move identifier
* Inline defog HM transfer preference check
HasMove is faster than getting moves & checking contains. Skips allocation by setting values directly.
* Extract more met location metadata checks: WasBredEgg
* Replace Console.Write* with Debug.Write*
There's no console output UI, so don't include them in release builds.
* Inline WasGiftEgg, WasEvent, and WasEventEgg logic
Adios legality tags that aren't entirely correct for the specific format. Just put the computations in EncounterFinder.
2021-06-23 03:23:48 +00:00
2022-06-18 18:04:24 +00:00
pkdata . Select ( GetPKM ) . CopyTo ( BD , IsSlotOverwriteProtected , start ) ;
BoxData = BD ;
return true ;
2019-09-03 02:30:58 +00:00
}
2022-06-18 18:04:24 +00:00
#endregion
}
2018-08-03 03:11:42 +00:00
2022-06-18 18:04:24 +00:00
public static class StorageUtil
{
2022-06-26 06:08:28 +00:00
public static bool CompressStorage ( this SaveFile sav , Span < byte > storage , out int storedCount , Span < int > slotPointers )
2019-09-03 02:30:58 +00:00
{
2022-06-18 18:04:24 +00:00
// keep track of empty slots, and only write them at the end if slots were shifted (no need otherwise).
var empty = new List < byte [ ] > ( ) ;
bool shiftedSlots = false ;
ushort ctr = 0 ;
int size = sav . SIZE_BOXSLOT ;
int count = sav . BoxSlotCount * sav . BoxCount ;
for ( int i = 0 ; i < count ; i + + )
2018-11-01 22:38:09 +00:00
{
2022-06-18 18:04:24 +00:00
int offset = sav . GetBoxSlotOffset ( i ) ;
if ( sav . IsPKMPresent ( storage [ offset . . ] ) )
2018-11-01 22:38:09 +00:00
{
2022-06-18 18:04:24 +00:00
if ( ctr ! = i ) // copy required
2018-11-01 22:38:09 +00:00
{
2022-06-18 18:04:24 +00:00
shiftedSlots = true ; // appending empty slots afterwards is now required since a rewrite was done
int destOfs = sav . GetBoxSlotOffset ( ctr ) ;
storage [ offset . . ( offset + size ) ] . CopyTo ( storage [ destOfs . . ( destOfs + size ) ] ) ;
SlotPointerUtil . UpdateRepointFrom ( ctr , i , slotPointers ) ;
2018-11-01 22:38:09 +00:00
}
2022-06-18 18:04:24 +00:00
ctr + + ;
continue ;
2018-11-01 22:38:09 +00:00
}
2022-06-18 18:04:24 +00:00
// pop out an empty slot; save all unused data & preserve order
var data = storage . Slice ( offset , size ) . ToArray ( ) ;
empty . Add ( data ) ;
}
2018-11-01 22:38:09 +00:00
2022-06-18 18:04:24 +00:00
storedCount = ctr ;
2018-11-01 22:38:09 +00:00
2022-06-18 18:04:24 +00:00
if ( ! shiftedSlots )
return false ;
2019-09-03 02:30:58 +00:00
2022-06-18 18:04:24 +00:00
for ( int i = ctr ; i < count ; i + + )
{
var data = empty [ i - ctr ] ;
int offset = sav . GetBoxSlotOffset ( i ) ;
data . CopyTo ( storage [ offset . . ] ) ;
2018-11-01 22:38:09 +00:00
}
2022-06-18 18:04:24 +00:00
return true ;
2016-06-20 04:22:43 +00:00
}
2023-01-22 04:02:33 +00:00
public static int FindSlotIndex ( this SaveFile sav , Func < PKM , bool > method , int maxCount )
{
for ( int i = 0 ; i < maxCount ; i + + )
{
var pk = sav . GetBoxSlotAtIndex ( i ) ;
if ( method ( pk ) )
return i ;
}
return - 1 ;
}
2016-06-20 04:22:43 +00:00
}