Rearrange how some memory info is checked

Split into different game contexts
This commit is contained in:
Kurt 2021-08-22 16:41:57 -07:00
parent b099b8d82c
commit a631e5cb4e
16 changed files with 782 additions and 631 deletions

View file

@ -28,7 +28,7 @@
if (pk is IMemoryOT o) if (pk is IMemoryOT o)
{ {
o.OT_Memory = 2; o.OT_Memory = 2;
o.OT_Feeling = Memories.GetRandomFeeling(2); o.OT_Feeling = MemoryContext6.GetRandomFeeling6(2);
o.OT_Intensity = 1; o.OT_Intensity = 1;
o.OT_TextVar = pk.XY ? 43 : 27; // riverside road : battling spot o.OT_TextVar = pk.XY ? 43 : 27; // riverside road : battling spot
} }
@ -45,7 +45,7 @@
// for lack of better randomization :) // for lack of better randomization :)
pk.OT_Memory = 63; pk.OT_Memory = 63;
pk.OT_Intensity = 6; pk.OT_Intensity = 6;
pk.OT_Feeling = Memories.GetRandomFeeling(pk.OT_Memory); pk.OT_Feeling = MemoryContext6.GetRandomFeeling6(pk.OT_Memory);
} }
} }
} }

View file

@ -1,490 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <summary>
/// Generation 6 Memory parameters &amp; validation
/// </summary>
public static class Memories
{
internal const int MAX_MEMORY_ID_XY = 64;
internal const int MAX_MEMORY_ID_AO = 69;
internal const int MAX_MEMORY_ID_SWSH = 89;
#region Tables
internal static readonly int[] Memory_NotXY =
{
65, // {0} was with {1} when (he/she) built a Secret Base. {4} that {3}.
66, // {0} participated in a contest with {1} and impressed many people. {4} that {3}.
67, // {0} participated in a contest with {1} and won the title. {4} that {3}.
68, // {0} soared through the sky with {1} and went to many different places. {4} that {3}.
69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}.
};
internal static readonly int[] Memory_NotAO =
{
11, // {0} went clothes shopping with {1}. {4} that {3}.
43, // {0} was impressed by the speed of the train it took with {1}. {4} that {3}.
44, // {0} encountered {2} with {1} using the Poké Radar. {4} that {3}.
56, // {0} was with {1} when (he/she) went to a boutique and tried on clothes, but (he/she) left the boutique without buying anything. {4} that {3}.
57, // {0} went to a nice restaurant with {1} and ate until it got totally full. {4} that {3}.
62, // {0} saw itself in a mirror in a mirror cave that it went to with {1}. {4} that {3}.
};
internal static readonly int[] Memory_NotSWSH =
{
// There's probably more. Send a pull request!
20, // {0} surfed across the water, carrying {1} on its back. {4} that {3}.
24, // {0} flew, carrying {1} on its back, to {2}. {4} that {3}.
35, // {0} proudly used Strength at {1}s instruction in... {2}. {4} that {3}.
36, // {0} proudly used Cut at {1}s instruction in... {2}. {4} that {3}.
37, // {0} shattered rocks to its hearts content at {1}s instruction in... {2}. {4} that {3}.
38, // {0} used Waterfall while carrying {1} on its back in... {2}. {4} that {3}.
69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}.
};
internal static readonly int[][] MoveSpecificMemories =
{
new[] {
20, // {0} surfed across the water, carrying {1} on its back. {4} that {3}.
24, // {0} flew, carrying {1} on its back, to {2}. {4} that {3}.
35, // {0} proudly used Strength at {1}s instruction in... {2}. {4} that {3}.
36, // {0} proudly used Cut at {1}s instruction in... {2}. {4} that {3}.
37, // {0} shattered rocks to its hearts content at {1}s instruction in... {2}. {4} that {3}.
38, // {0} used Waterfall while carrying {1} on its back in... {2}. {4} that {3}.
69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}.
},
new[] { 57, 19, 70, 15, 249, 127, 291}, // Move IDs
};
internal static readonly int[] LocationsWithPokeCenter_XY =
{
// Kalos locations with a PKMN CENTER
018, // Santalune City
022, // Lumiose City
030, // Camphrier Town
040, // Cyllage City
044, // Ambrette Town
052, // Geosenge Towny
058, // Shalour City
064, // Coumarine City
070, // Laverre City
076, // Dendemille Town
086, // Anistar City
090, // Couriway Town
094, // Snowbelle City
106, // Pokémon League (X/Y)
};
internal static readonly int[] LocationsWithPokeCenter_AO =
{
// Hoenn locations with a PKMN CENTER
172, // Oldale Town
174, // Dewford Town
176, // Lavaridge Town
178, // Fallarbor Town
180, // Verdanturf Town
182, // Pacifidlog Town
184, // Petalburg City
186, // Slateport City
188, // Mauville City
190, // Rustboro City
192, // Fortree City
194, // Lilycove City
196, // Mossdeep City
198, // Sootopolis City
200, // Ever Grande City
202, // Pokémon League (OR/AS)
};
private static ICollection<int> GetPokeCenterLocations(GameVersion game)
{
return GameVersion.XY.Contains(game) ? LocationsWithPokeCenter_XY : LocationsWithPokeCenter_AO;
}
public static bool GetHasPokeCenterLocation(GameVersion game, int loc)
{
if (game == GameVersion.Gen6)
return GetHasPokeCenterLocation(GameVersion.X, loc) || GetHasPokeCenterLocation(GameVersion.AS, loc);
return GetPokeCenterLocations(game).Contains(loc);
}
private static readonly byte[] MemoryMinIntensity =
{
0, 1, 1, 1, 1, 2, 2, 2, 2, 2,
2, 2, 2, 2, 3, 3, 3, 3, 4, 4,
3, 3, 3, 3, 3, 3, 3, 4, 5, 5,
5, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 1, 3, 2, 2, 4, 3, 4, 4,
4, 4, 2, 4, 2, 4, 3, 3, 4, 2,
3, 3, 3, 3, 3, 2, 3, 4, 4, 2,
};
public static int GetMemoryRarity(int memory) => (uint) memory >= MemoryRandChance.Length ? -1 : MemoryRandChance[memory];
private static readonly byte[] MemoryRandChance =
{
000, 100, 100, 100, 100, 005, 005, 005, 005, 005,
005, 005, 005, 005, 010, 020, 010, 001, 050, 030,
005, 005, 020, 005, 005, 005, 001, 050, 100, 050,
050, 002, 002, 005, 005, 005, 005, 005, 005, 002,
020, 020, 005, 010, 001, 001, 050, 030, 020, 020,
010, 010, 001, 010, 001, 050, 030, 030, 030, 002,
050, 020, 020, 020, 020, 010, 010, 050, 020, 005,
};
/// <summary>
/// 24bits of flags allowing certain feelings for a given memory index.
/// </summary>
private static readonly uint[] MemoryFeelings =
{
0x000000, 0x04CBFD, 0x004BFD, 0x04CBFD, 0x04CBFD, 0xFFFBFB, 0x84FFF9, 0x47FFFF, 0xBF7FFA, 0x7660B0,
0x80BDF9, 0x88FB7A, 0x083F79, 0x0001FE, 0xCFEFFF, 0x84EBAF, 0xB368B0, 0x091F7E, 0x0320A0, 0x080DDD,
0x081A7B, 0x404030, 0x0FFFFF, 0x9A08BC, 0x089A7B, 0x0032AA, 0x80FF7A, 0x0FFFFF, 0x0805FD, 0x098278,
0x0B3FFF, 0x8BBFFA, 0x8BBFFE, 0x81A97C, 0x8BB97C, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0xAC3ABE,
0xBFFFFF, 0x8B837C, 0x848AFA, 0x88FFFE, 0x8B0B7C, 0xB76AB2, 0x8B1FFF, 0xBE7AB8, 0xB77EB8, 0x8C9FFD,
0xBF9BFF, 0xF408B0, 0xBCFE7A, 0x8F3F72, 0x90DB7A, 0xBCEBFF, 0xBC5838, 0x9C3FFE, 0x9CFFFF, 0x96D83A,
0xB770B0, 0x881F7A, 0x839F7A, 0x839F7A, 0x839F7A, 0x53897F, 0x41BB6F, 0x0C35FF, 0x8BBF7F, 0x8BBF7F,
};
#endregion
#region Tables for Gen8
public static bool IsInvalidGenLoc8(int memory, int loc, int egg, int variable)
{
if (variable > 255)
return true;
var arg = (byte)variable;
if (loc > 255) // gift
return memory != 3 || !PossibleGeneralLocations.Contains(arg);
if (memory == 2 && egg == 0)
return true;
if (loc is Encounters8Nest.SharedNest)
return !PossibleGeneralLocations.Contains(arg) || arg is 79; // dangerous place - all locations are Y-Comm locked
if (SingleGenLocAreas.TryGetValue((byte)loc, out var val))
return arg != val;
if (MultiGenLocAreas.TryGetValue((byte)loc, out var arr))
return !arr.Contains(arg);
return false;
}
public static bool IsInvalidGenLoc8Other(int memory, int variable)
{
if (variable > byte.MaxValue)
return true;
var arg = (byte)variable;
return memory switch
{
_ => !PossibleGeneralLocations.Contains(arg),
};
}
// {met, values allowed}
private static readonly Dictionary<byte, byte[]> MultiGenLocAreas = new()
{
// town of Postwick
// first town, at home, friend's house
{6, new byte[] {1, 2, 3}},
// town of Wedgehurst
// someone's house, boutique, simple town, Pokémon Center
{14, new byte[] {4, 6, 8, 9}},
// Route 2
// lab, tranquil road, lakeside road
{18, new byte[] {16, 44, 71}},
// city of Motostoke
// boutique, Pokémon Center, hotel, Pokémon Gym, stylish café, hair salon, town with a mysterious air
{20, new byte[] {6, 9, 11, 20, 24, 30, 35}},
// Motostoke Stadium
// Pokémon Gym, stadium
{24, new byte[] {20, 73}},
// town of Turffield
// Pokémon Center, town with a mysterious air
{34, new byte[] {9, 12}},
// Route 5
// tranquil road, Pokémon Nursery
{40, new byte[] {44, 74}},
// town of Hulbury
// someones house, Pokémon Center, restaurant, seaside town
{44, new byte[] {4, 9, 31, 33}},
// city of Hammerlocke
// someones house, boutique, Pokémon Center, Pokémon Gym, stylish café, hair salon, town with a mysterious air
{56, new byte[] {4, 6, 9, 20, 24, 30, 35}},
// town of Stow-on-Side
// someones house, Pokémon Center, town in the mountains
{70, new byte[] {4, 9, 76}},
// town of Ballonlea
// someones house, Pokémon Center, town with a mysterious air
{78, new byte[] {4, 9, 12}},
// town of Circhester
// someones house, boutique, Pokémon Center, hotel, hair salon, restaurant, snowcapped town
{96, new byte[] {4, 6, 9, 11, 30, 31, 37}},
// town of Spikemuth
// Pokémon Center, run-down town
{102, new byte[] {9, 77}},
// city of Wyndon
// someones house, boutique, Pokémon Center, hotel, large town, stylish café, hair salon
{110, new byte[] {4, 6, 9, 11, 22, 24, 30}},
// town of Freezington
// someones house, snowcapped town
{206, new byte[] {04, 37}},
};
// {met, value allowed}
private static readonly Dictionary<byte, byte> SingleGenLocAreas = new()
{
{008, 41}, // Slumbering Weald, forest
{012, 44}, // Route 1, tranquil road
{016, 28}, // Wedgehurst Station, train station
{022, 28}, // Motostoke Station, train station
{028, 44}, // Route 3, tranquil road
{030, 75}, // Galar Mine, mine
{032, 44}, // Route 4, tranquil road
{036, 73}, // Turffield Stadium, stadium
{046, 28}, // Hulbury Station, train station
{048, 73}, // Hulbury Stadium, stadium
{052, 35}, // Motostoke Outskirts, town with a mysterious air
{054, 75}, // Galar Mine No. 2, mine
{058, 28}, // Hammerlocke Station, train station
{060, 73}, // Hammerlocke Stadium, stadium
{064, 79}, // Energy Plant, dangerous place
{066, 79}, // the tower summit, dangerous place
{068, 47}, // Route 6, rugged mountain pass
{072, 73}, // Stow-on-Side Stadium, stadium
{076, 41}, // Glimwood Tangle, forest
{080, 73}, // Ballonlea Stadium, stadium
{084, 44}, // Route 7, tranquil road
{086, 47}, // Route 8, rugged mountain pass
{088, 53}, // Route 8 (on Steamdrift Way), snow-swept road
{090, 53}, // Route 9, snow-swept road
{092, 53}, // Route 9 (in Circhester Bay), snow-swept road
{094, 53}, // Route 9 (in Outer Spikemuth), snow-swept road
{098, 73}, // Circhester Stadium, stadium
{104, 78}, // Route 9 Tunnel, tunnel
{106, 53}, // Route 10, snow-swept road
{108, 28}, // White Hill Station, train station
{112, 28}, // Wyndon Station, train station
{114, 38}, // Wyndon Stadium (at the Pokémon League HQ), Pokémon League
{116, 38}, // Wyndon Stadium (in a locker room), Pokémon League
{120, 44}, // Meetup Spot, tranquil road
{122, 72}, // Rolling Fields, vast field
{124, 72}, // Dappled Grove, vast field
{126, 72}, // Watchtower Ruins, vast field
{128, 72}, // East Lake Axewell, vast field
{130, 72}, // West Lake Axewell, vast field
{132, 72}, // Axews Eye, vast field
{134, 72}, // South Lake Miloch, vast field
{136, 72}, // near the Giants Seat, vast field
{138, 72}, // North Lake Miloch, vast field
{140, 72}, // Motostoke Riverbank, vast field
{142, 72}, // Bridge Field, vast field
{144, 72}, // Stony Wilderness, vast field
{146, 72}, // Dusty Bowl, vast field
{148, 72}, // around the Giants Mirror, vast field
{150, 72}, // the Hammerlocke Hills, vast field
{152, 72}, // near the Giants Cap, vast field
{154, 72}, // Lake of Outrage, vast field
{156, 28}, // Wild Area Station, train station
{158, 34}, // Battle Tower, tall building
{160, 34}, // Rose Tower, tall building
{164, 72}, // Fields of Honor, vast field
{166, 50}, // Soothing Wetlands, muddy road
{168, 41}, // Forest of Focus, forest
{170, 49}, // Challenge Beach, seaside road
{172, 40}, // Brawlers Cave, cave
{174, 76}, // Challenge Road, town in the mountains
{176, 40}, // Courageous Cavern, cave
{178, 49}, // Loop Lagoon, seaside road
{180, 72}, // Training Lowlands, vast field
{182, 40}, // Warm-Up Tunnel, cave
{184, 51}, // Potbottom Desert, sand-swept road
{186, 49}, // Workout Sea, seaside road
{188, 49}, // Stepping-Stone Sea, seaside road
{190, 49}, // Insular Sea, seaside road
{192, 49}, // Honeycalm Sea, seaside road
{194, 49}, // Honeycalm Island, seaside road
{196, 29}, // Master Dojo, battling spot
{198, 34}, // Tower of Darkness, tall building
{200, 34}, // Tower of Waters, tall building
{202, 28}, // Armor Station, train station
{204, 53}, // Slippery Slope, snow-swept road
{208, 53}, // Frostpoint Field, snow-swept road
{210, 72}, // Giants Bed, vast field
{212, 48}, // Old Cemetery, stone-lined area
{214, 53}, // Snowslide Slope, snow-swept road
{216, 40}, // Tunnel to the Top, cave
{218, 53}, // the Path to the Peak, snow-swept road
{220, 65}, // Crown Shrine, mystical place
{222, 44}, // Giants Foot, tranquil road
{224, 40}, // Roaring-Sea Caves, cave
{226, 49}, // Frigid Sea, seaside road
{228, 72}, // Three-Point Pass, vast field
{230, 72}, // Ballimere Lake, vast field
{232, 40}, // Lakeside Cave, cave
{234, 72}, // Dyna Tree Hill, vast field
{236, 65}, // Rock Peak Ruins, mystical place
{238, 65}, // Iceberg Ruins, mystical place
{240, 65}, // Iron Ruins, mystical place
{242, 65}, // Split-Decision Ruins, mystical place
{244, 40}, // Max Lair, cave
{246, 28}, // Crown Tundra Station, train station
};
private static readonly HashSet<byte> PossibleGeneralLocations = new()
{
01, 02, 03, 04, 06, 08, 09,
11, 12, 16,
20, 22, 24, 28, 29,
30, 31, 33, 34, 35, 37, 38,
40, 41, 44, 47, 48, 49,
50, 51, 53,
65,
71, 72, 73, 74, 75, 76, 77, 78, 79,
};
#endregion
private static bool IsGeneralLocationMemoryMet(int memory) => memory is (1 or 2 or 3);
internal static readonly HashSet<int> MemoryGeneral = new() { 1, 2, 3, 4, 19, 24, 31, 32, 33, 35, 36, 37, 38, 39, 42, 52, 59, 70, 86 };
private static readonly HashSet<int> MemorySpecific = new() { 6 };
private static readonly HashSet<int> MemoryMove = new() { 12, 16, 48, 49, 80, 81, 89 };
private static readonly HashSet<int> MemoryItem = new() { 5, 15, 26, 34, 40, 51, 84, 88 };
private static readonly HashSet<int> MemorySpecies = new() { 7, 9, 13, 14, 17, 21, 18, 25, 29, 44, 45, 50, 60, 70, 71, 72, 75, 82, 83, 87 };
internal static readonly ushort[] KeyItemUsableObserve6 =
{
775, // Eon Flute
};
internal static readonly Dictionary<int, ushort[]> KeyItemMemoryArgsGen6 = new()
{
{(int) Species.Shaymin, new ushort[] {466}}, // Gracidea
{(int) Species.Tornadus, new ushort[] {638}}, // Reveal Glass
{(int) Species.Thundurus, new ushort[] {638}}, // Reveal Glass
{(int) Species.Landorus, new ushort[] {638}}, // Reveal Glass
{(int) Species.Kyurem, new ushort[] {628, 629}}, // DNA Splicers
{(int) Species.Hoopa, new ushort[] {765}}, // Prison Bottle
};
internal static readonly Dictionary<int, ushort[]> KeyItemMemoryArgsGen8 = new()
{
{(int) Species.Rotom, new ushort[] {1278}}, // Rotom Catalog
{(int) Species.Kyurem, new ushort[] {628, 629}}, // DNA Splicers
{(int) Species.Necrozma, new ushort[] {943, 944, 945, 946}}, // N-Lunarizer / N-Solarizer
{(int) Species.Calyrex, new ushort[] {1590, 1591}}, // Reigns of Unity
};
public static IEnumerable<ushort> KeyItemArgValues => KeyItemUsableObserve6.Concat(KeyItemMemoryArgsGen6.Values.Concat(KeyItemMemoryArgsGen8.Values).SelectMany(z => z)).Distinct();
public static MemoryArgType GetMemoryArgType(int memory, int format)
{
if (MemoryGeneral.Contains(memory)) return MemoryArgType.GeneralLocation;
if (MemorySpecific.Contains(memory) && format == 6) return MemoryArgType.SpecificLocation;
if (MemoryItem.Contains(memory)) return MemoryArgType.Item;
if (MemoryMove.Contains(memory)) return MemoryArgType.Move;
if (MemorySpecies.Contains(memory)) return MemoryArgType.Species;
return MemoryArgType.None;
}
public static bool CanHaveFeeling(int memory, int feeling)
{
if (memory >= MemoryFeelings.Length)
return false;
return (MemoryFeelings[memory] & (1 << feeling)) != 0;
}
public static bool CanHaveIntensity(int memory, int intensity)
{
if (memory >= MemoryFeelings.Length)
return false;
return MemoryMinIntensity[memory] <= intensity;
}
public static int GetRandomFeeling(int memory, int max = 24)
{
var bits = MemoryFeelings[memory];
var rnd = Util.Rand;
while (true)
{
int feel = rnd.Next(max);
if ((bits & (1 << feel)) != 0)
return feel;
}
}
public static int GetMinimumIntensity(int memory)
{
if (memory > MemoryMinIntensity.Length)
return -1;
return MemoryMinIntensity[memory];
}
public static IEnumerable<ushort> GetMemoryItemParams(int format)
{
return format switch
{
6 or 7 => Legal.HeldItem_AO.Distinct()
.Concat(KeyItemArgValues)
.Concat(Legal.TMHM_AO.Take(100).Select(z => (ushort)z))
.Where(z => z < Legal.MaxItemID_6_AO),
8 => Legal.HeldItem_AO.Concat(Legal.HeldItems_SWSH).Distinct()
.Concat(KeyItemArgValues)
.Concat(Legal.TMHM_AO.Take(100).Select(z => (ushort)z))
.Where(z => z < Legal.MaxItemID_8_R2),
_ => System.Array.Empty<ushort>(),
};
}
}
public readonly struct MemoryVariableSet
{
public readonly string Handler;
public readonly int MemoryID;
public readonly int Variable;
public readonly int Intensity;
public readonly int Feeling;
private MemoryVariableSet(string handler, int m, int v, int i, int f)
{
Handler = handler;
MemoryID = m;
Variable = v;
Intensity = i;
Feeling = f;
}
public static MemoryVariableSet Read(ITrainerMemories pkm, int handler) => handler switch
{
0 => new MemoryVariableSet(LegalityCheckStrings.L_XOT, pkm.OT_Memory, pkm.OT_TextVar, pkm.OT_Intensity, pkm.OT_Feeling), // OT
1 => new MemoryVariableSet(LegalityCheckStrings.L_XHT, pkm.HT_Memory, pkm.HT_TextVar, pkm.HT_Intensity, pkm.HT_Feeling), // HT
_ => new MemoryVariableSet(LegalityCheckStrings.L_XOT, 0, 0, 0, 0),
};
public bool Equals(MemoryVariableSet v) => v.Handler == Handler
&& v.MemoryID == MemoryID
&& v.Variable == Variable
&& v.Intensity == Intensity
&& v.Feeling == Feeling;
public override bool Equals(object obj) => obj is MemoryVariableSet v && Equals(v);
public override int GetHashCode() => -1;
public static bool operator ==(MemoryVariableSet left, MemoryVariableSet right) => left.Equals(right);
public static bool operator !=(MemoryVariableSet left, MemoryVariableSet right) => !(left == right);
}
}

View file

@ -0,0 +1,38 @@
using System.Collections.Generic;
namespace PKHeX.Core
{
/// <summary>
/// Generation 6 Memory parameters &amp; validation
/// </summary>
public static class Memories
{
public static readonly MemoryContext6 Memory6 = new();
public static readonly MemoryContext8 Memory8 = new();
internal static readonly HashSet<int> MemoryGeneral = new() { 1, 2, 3, 4, 19, 24, 31, 32, 33, 35, 36, 37, 38, 39, 42, 52, 59, 70, 86 };
private static readonly HashSet<int> MemorySpecific = new() { 6 };
private static readonly HashSet<int> MemoryMove = new() { 12, 16, 48, 49, 80, 81, 89 };
private static readonly HashSet<int> MemoryItem = new() { 5, 15, 26, 34, 40, 51, 84, 88 };
private static readonly HashSet<int> MemorySpecies = new() { 7, 9, 13, 14, 17, 21, 18, 25, 29, 44, 45, 50, 60, 70, 71, 72, 75, 82, 83, 87 };
public static MemoryArgType GetMemoryArgType(int memory, int format)
{
if (MemoryGeneral.Contains(memory)) return MemoryArgType.GeneralLocation;
if (MemorySpecific.Contains(memory) && format == 6) return MemoryArgType.SpecificLocation;
if (MemoryItem.Contains(memory)) return MemoryArgType.Item;
if (MemoryMove.Contains(memory)) return MemoryArgType.Move;
if (MemorySpecies.Contains(memory)) return MemoryArgType.Species;
return MemoryArgType.None;
}
public static MemoryContext GetContext(int format) => format switch
{
6 or 7 => Memory6,
_ => Memory8,
};
public static IEnumerable<ushort> GetMemoryItemParams(int format) => GetContext(format).GetMemoryItemParams();
}
}

View file

@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace PKHeX.Core
{
public abstract class MemoryContext
{
public abstract IEnumerable<ushort> GetMemoryItemParams();
public abstract IEnumerable<ushort> GetKeyItemParams();
public abstract bool CanUseItemGeneric(int item);
public abstract bool IsUsedKeyItemUnspecific(int item);
public abstract bool IsUsedKeyItemSpecific(int item, int species);
public abstract bool CanBuyItem(int item, GameVersion version);
public virtual bool CanWinRotoLoto(int item) => false;
public virtual bool CanPlantBerry(int item) => false;
public abstract bool CanHoldItem(int item);
public abstract bool CanObtainMemory(int memory);
public abstract bool CanObtainMemoryOT(GameVersion pkmVersion, int memory);
public abstract bool CanObtainMemoryHT(GameVersion pkmVersion, int memory);
public abstract bool HasPokeCenter(GameVersion version, int location);
public abstract bool IsInvalidGeneralLocationMemoryValue(int memory, int variable, IEncounterTemplate enc, PKM pk);
public abstract bool CanHaveIntensity(int memory, int intensity);
public abstract bool CanHaveFeeling(int memory, int feeling);
public abstract int GetMinimumIntensity(int memory);
}
}

View file

@ -0,0 +1,116 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
public sealed partial class MemoryContext6 : MemoryContext
{
private const int MAX_MEMORY_ID_XY = 64;
private const int MAX_MEMORY_ID_AO = 69;
private static ICollection<int> GetPokeCenterLocations(GameVersion game)
{
return GameVersion.XY.Contains(game) ? LocationsWithPokeCenter_XY : LocationsWithPokeCenter_AO;
}
public static bool GetHasPokeCenterLocation(GameVersion game, int loc)
{
if (game == GameVersion.Any)
return GetHasPokeCenterLocation(GameVersion.X, loc) || GetHasPokeCenterLocation(GameVersion.AS, loc);
return GetPokeCenterLocations(game).Contains(loc);
}
public static int GetMemoryRarity(int memory) => (uint)memory >= MemoryRandChance.Length ? -1 : MemoryRandChance[memory];
public override IEnumerable<ushort> GetKeyItemParams() => KeyItemUsableObserve6.Concat(KeyItemMemoryArgsGen6.Values.SelectMany(z => z)).Distinct();
public override bool CanUseItemGeneric(int item)
{
// Key Item usage while in party on another species.
if (KeyItemUsableObserve6.Contains((ushort)item))
return true;
if (KeyItemMemoryArgsGen6.Values.Any(z => z.Contains((ushort)item)))
return true;
return false;
}
public override IEnumerable<ushort> GetMemoryItemParams() => Legal.HeldItem_AO.Distinct()
.Concat(GetKeyItemParams())
.Concat(Legal.TMHM_AO.Take(100).Select(z => (ushort)z))
.Where(z => z < Legal.MaxItemID_6_AO);
public override bool IsUsedKeyItemUnspecific(int item) => KeyItemUsableObserve6.Contains((ushort)item);
public override bool IsUsedKeyItemSpecific(int item, int species) => KeyItemMemoryArgsGen6.TryGetValue(species, out var value) && value.Contains((ushort)item);
public override bool CanPlantBerry(int item) => Legal.Pouch_Berry_XY.Contains((ushort)item);
public override bool CanHoldItem(int item) => Legal.HeldItem_AO.Contains((ushort)item);
public override bool CanObtainMemoryOT(GameVersion pkmVersion, int memory) => pkmVersion switch
{
GameVersion.X or GameVersion.Y => CanObtainMemoryXY(memory),
GameVersion.OR or GameVersion.AS => CanObtainMemoryAO(memory),
_ => false,
};
public override bool CanObtainMemory(int memory) => memory <= MAX_MEMORY_ID_AO;
public override bool HasPokeCenter(GameVersion version, int location) => GetHasPokeCenterLocation(version, location);
public override bool IsInvalidGeneralLocationMemoryValue(int memory, int variable, IEncounterTemplate enc, PKM pk)
{
return false; // todo
}
private static bool CanObtainMemoryAO(int memory) => memory <= MAX_MEMORY_ID_AO && !Memory_NotAO.Contains(memory);
private static bool CanObtainMemoryXY(int memory) => memory <= MAX_MEMORY_ID_XY && !Memory_NotXY.Contains(memory);
public override bool CanObtainMemoryHT(GameVersion pkmVersion, int memory) => CanObtainMemory(memory);
public override bool CanBuyItem(int item, GameVersion version)
{
if (version is GameVersion.Any)
return PurchaseableItemAO.Contains((ushort)item) || PurchaseableItemXY.Contains((ushort)item);
if (version is GameVersion.X or GameVersion.Y)
return PurchaseableItemXY.Contains((ushort)item);
if (version is GameVersion.AS or GameVersion.OR)
return PurchaseableItemAO.Contains((ushort)item);
return false;
}
public static bool CanHaveFeeling6(int memory, int feeling)
{
if (memory >= MemoryFeelings.Length)
return false;
return (MemoryFeelings[memory] & (1 << feeling)) != 0;
}
public static bool CanHaveIntensity6(int memory, int intensity)
{
if (memory >= MemoryFeelings.Length)
return false;
return MemoryMinIntensity[memory] <= intensity;
}
public static int GetRandomFeeling6(int memory, int max = 24)
{
var bits = MemoryFeelings[memory];
var rnd = Util.Rand;
while (true)
{
int feel = rnd.Next(max);
if ((bits & (1 << feel)) != 0)
return feel;
}
}
public static int GetMinimumIntensity6(int memory)
{
if (memory >= MemoryMinIntensity.Length)
return -1;
return MemoryMinIntensity[memory];
}
public override bool CanHaveIntensity(int memory, int intensity) => CanHaveIntensity6(memory, intensity);
public override bool CanHaveFeeling(int memory, int feeling) => CanHaveFeeling6(memory, feeling);
public override int GetMinimumIntensity(int memory) => GetMinimumIntensity6(memory);
}
}

View file

@ -0,0 +1,163 @@
using System.Collections.Generic;
namespace PKHeX.Core
{
// data tables stored separately!
public partial class MemoryContext6
{
private static readonly int[] Memory_NotXY =
{
65, // {0} was with {1} when (he/she) built a Secret Base. {4} that {3}.
66, // {0} participated in a contest with {1} and impressed many people. {4} that {3}.
67, // {0} participated in a contest with {1} and won the title. {4} that {3}.
68, // {0} soared through the sky with {1} and went to many different places. {4} that {3}.
69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}.
};
private static readonly int[] Memory_NotAO =
{
11, // {0} went clothes shopping with {1}. {4} that {3}.
43, // {0} was impressed by the speed of the train it took with {1}. {4} that {3}.
44, // {0} encountered {2} with {1} using the Poké Radar. {4} that {3}.
56, // {0} was with {1} when (he/she) went to a boutique and tried on clothes, but (he/she) left the boutique without buying anything. {4} that {3}.
57, // {0} went to a nice restaurant with {1} and ate until it got totally full. {4} that {3}.
62, // {0} saw itself in a mirror in a mirror cave that it went to with {1}. {4} that {3}.
};
internal static readonly int[][] MoveSpecificMemories =
{
new[] {
20, // {0} surfed across the water, carrying {1} on its back. {4} that {3}.
24, // {0} flew, carrying {1} on its back, to {2}. {4} that {3}.
35, // {0} proudly used Strength at {1}s instruction in... {2}. {4} that {3}.
36, // {0} proudly used Cut at {1}s instruction in... {2}. {4} that {3}.
37, // {0} shattered rocks to its hearts content at {1}s instruction in... {2}. {4} that {3}.
38, // {0} used Waterfall while carrying {1} on its back in... {2}. {4} that {3}.
69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}.
},
new[] { 57, 19, 70, 15, 249, 127, 291}, // Move IDs
};
/// <summary>
/// Kalos locations with a Pokémon Center
/// </summary>
private static readonly int[] LocationsWithPokeCenter_XY =
{
// Kalos locations with a PKMN CENTER
018, // Santalune City
022, // Lumiose City
030, // Camphrier Town
040, // Cyllage City
044, // Ambrette Town
052, // Geosenge Towny
058, // Shalour City
064, // Coumarine City
070, // Laverre City
076, // Dendemille Town
086, // Anistar City
090, // Couriway Town
094, // Snowbelle City
106, // Pokémon League (X/Y)
};
/// <summary>
/// Hoenn locations with a Pokémon Center
/// </summary>
private static readonly int[] LocationsWithPokeCenter_AO =
{
// Hoenn locations with a PKMN CENTER
172, // Oldale Town
174, // Dewford Town
176, // Lavaridge Town
178, // Fallarbor Town
180, // Verdanturf Town
182, // Pacifidlog Town
184, // Petalburg City
186, // Slateport City
188, // Mauville City
190, // Rustboro City
192, // Fortree City
194, // Lilycove City
196, // Mossdeep City
198, // Sootopolis City
200, // Ever Grande City
202, // Pokémon League (OR/AS)
};
private static readonly byte[] MemoryMinIntensity =
{
0, 1, 1, 1, 1, 2, 2, 2, 2, 2,
2, 2, 2, 2, 3, 3, 3, 3, 4, 4,
3, 3, 3, 3, 3, 3, 3, 4, 5, 5,
5, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 1, 3, 2, 2, 4, 3, 4, 4,
4, 4, 2, 4, 2, 4, 3, 3, 4, 2,
3, 3, 3, 3, 3, 2, 3, 4, 4, 2,
};
private static readonly byte[] MemoryRandChance =
{
000, 100, 100, 100, 100, 005, 005, 005, 005, 005,
005, 005, 005, 005, 010, 020, 010, 001, 050, 030,
005, 005, 020, 005, 005, 005, 001, 050, 100, 050,
050, 002, 002, 005, 005, 005, 005, 005, 005, 002,
020, 020, 005, 010, 001, 001, 050, 030, 020, 020,
010, 010, 001, 010, 001, 050, 030, 030, 030, 002,
050, 020, 020, 020, 020, 010, 010, 050, 020, 005,
};
/// <summary>
/// 24bits of flags allowing certain feelings for a given memory index.
/// </summary>
private static readonly uint[] MemoryFeelings =
{
0x000000, 0x04CBFD, 0x004BFD, 0x04CBFD, 0x04CBFD, 0xFFFBFB, 0x84FFF9, 0x47FFFF, 0xBF7FFA, 0x7660B0,
0x80BDF9, 0x88FB7A, 0x083F79, 0x0001FE, 0xCFEFFF, 0x84EBAF, 0xB368B0, 0x091F7E, 0x0320A0, 0x080DDD,
0x081A7B, 0x404030, 0x0FFFFF, 0x9A08BC, 0x089A7B, 0x0032AA, 0x80FF7A, 0x0FFFFF, 0x0805FD, 0x098278,
0x0B3FFF, 0x8BBFFA, 0x8BBFFE, 0x81A97C, 0x8BB97C, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0xAC3ABE,
0xBFFFFF, 0x8B837C, 0x848AFA, 0x88FFFE, 0x8B0B7C, 0xB76AB2, 0x8B1FFF, 0xBE7AB8, 0xB77EB8, 0x8C9FFD,
0xBF9BFF, 0xF408B0, 0xBCFE7A, 0x8F3F72, 0x90DB7A, 0xBCEBFF, 0xBC5838, 0x9C3FFE, 0x9CFFFF, 0x96D83A,
0xB770B0, 0x881F7A, 0x839F7A, 0x839F7A, 0x839F7A, 0x53897F, 0x41BB6F, 0x0C35FF, 0x8BBF7F, 0x8BBF7F,
};
private static readonly Dictionary<int, ushort[]> KeyItemMemoryArgsGen6 = new()
{
{(int) Species.Shaymin, new ushort[] {466}}, // Gracidea
{(int) Species.Tornadus, new ushort[] {638}}, // Reveal Glass
{(int) Species.Thundurus, new ushort[] {638}}, // Reveal Glass
{(int) Species.Landorus, new ushort[] {638}}, // Reveal Glass
{(int) Species.Kyurem, new ushort[] {628, 629}}, // DNA Splicers
{(int) Species.Hoopa, new ushort[] {765}}, // Prison Bottle
};
private static readonly ushort[] KeyItemUsableObserve6 =
{
775, // Eon Flute
};
private static readonly HashSet<ushort> PurchaseableItemXY = new()
{
002, 003, 004, 006, 007, 008, 009, 010, 011, 012,
013, 014, 015, 017, 018, 019, 020, 021, 022, 023,
024, 025, 026, 027, 028, 034, 035, 036, 037, 045,
046, 047, 048, 049, 052, 055, 056, 057, 058, 059,
060, 061, 062, 076, 077, 078, 079, 082, 084, 085,
254, 255, 314, 315, 316, 317, 318, 319, 320, 334,
338, 341, 342, 343, 345, 347, 352, 355, 360, 364,
365, 377, 379, 395, 402, 403, 405, 411, 618,
};
private static readonly HashSet<ushort> PurchaseableItemAO = new()
{
002, 003, 004, 006, 007, 008, 009, 010, 011, 013,
014, 015, 017, 018, 019, 020, 021, 022, 023, 024,
025, 026, 027, 028, 034, 035, 036, 037, 045, 046,
047, 048, 049, 052, 055, 056, 057, 058, 059, 060,
061, 062, 063, 064, 076, 077, 078, 079, 254, 255,
314, 315, 316, 317, 318, 319, 320, 328, 336, 341,
342, 343, 344, 347, 352, 360, 365, 367, 369, 374,
379, 384, 395, 398, 400, 403, 405, 409, 577, 692,
694,
};
}
}

View file

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
public sealed partial class MemoryContext8 : MemoryContext
{
private const int MAX_MEMORY_ID_SWSH = 89;
private static ICollection<int> GetPokeCenterLocations(GameVersion game)
{
return GameVersion.SWSH.Contains(game) ? LocationsWithPokeCenter_SWSH : Array.Empty<int>();
}
public static bool GetHasPokeCenterLocation(GameVersion game, int loc)
{
if (game == GameVersion.Any)
return GetHasPokeCenterLocation(GameVersion.SW, loc);
return GetPokeCenterLocations(game).Contains(loc);
}
public override IEnumerable<ushort> GetKeyItemParams() => (KeyItemMemoryArgsGen8.Values).SelectMany(z => z).Distinct();
public override IEnumerable<ushort> GetMemoryItemParams() => Legal.HeldItem_AO.Concat(Legal.HeldItems_SWSH).Distinct()
.Concat(GetKeyItemParams())
.Concat(Legal.TMHM_AO.Take(100).Select(z => (ushort)z))
.Where(z => z < Legal.MaxItemID_8_R2);
public override bool CanUseItemGeneric(int item) => true; // todo
public override bool IsUsedKeyItemUnspecific(int item) => false;
public override bool IsUsedKeyItemSpecific(int item, int species) => KeyItemMemoryArgsGen8.TryGetValue(species, out var value) && value.Contains((ushort)item);
public override bool CanHoldItem(int item) => Legal.HeldItems_SWSH.Contains((ushort)item);
public override bool CanObtainMemoryOT(GameVersion pkmVersion, int memory) => pkmVersion switch
{
GameVersion.SW or GameVersion.SH => CanObtainMemorySWSH(memory),
_ => false,
};
public override bool CanObtainMemoryHT(GameVersion pkmVersion, int memory) => CanObtainMemorySWSH(memory);
public override bool CanObtainMemory(int memory) => CanObtainMemorySWSH(memory);
public override bool HasPokeCenter(GameVersion version, int location) => GetHasPokeCenterLocation(version, location);
public override bool IsInvalidGeneralLocationMemoryValue(int memory, int variable, IEncounterTemplate enc, PKM pk)
{
if (!Memories.MemoryGeneral.Contains(memory))
return false;
if (memory is 1 or 2 or 3) // Encounter only
return IsInvalidGenLoc8(memory, pk.Met_Location, pk.Egg_Location, variable, enc);
return IsInvalidGenLoc8Other(memory, variable);
}
private static bool CanObtainMemorySWSH(int memory) => memory <= MAX_MEMORY_ID_SWSH && !Memory_NotSWSH.Contains(memory);
public override bool CanWinRotoLoto(int item) => true;
public override bool CanBuyItem(int item, GameVersion version)
{
if (item is 1085) // Bob's Food Tin
return version is GameVersion.SW or GameVersion.Any;
if (item is 1086) // Bach's Food Tin
return version is GameVersion.SH or GameVersion.Any;
return PurchaseableItemSWSH.Contains((ushort)item);
}
private static bool IsInvalidGenLoc8(int memory, int loc, int egg, int variable, IEncounterTemplate enc)
{
if (variable > 255)
return true;
switch (memory)
{
case 1 when enc.EggEncounter:
case 2 when !enc.EggEncounter:
case 3 when enc is not (EncounterTrade or EncounterStatic {Gift: true} or WC8 {IsHOMEGift: false}):
return true;
}
var arg = (byte)variable;
if (loc > 255) // gift
return memory != 3 || !PossibleGeneralLocations8.Contains(arg);
if (memory == 2 && egg == 0)
return true;
if (loc is Encounters8Nest.SharedNest)
return !PossibleGeneralLocations8.Contains(arg) || arg is 79; // dangerous place - all locations are Y-Comm locked
if (SingleGenLocAreas.TryGetValue((byte)loc, out var val))
return arg != val;
if (MultiGenLocAreas.TryGetValue((byte)loc, out var arr))
return !arr.Contains(arg);
return false;
}
private static bool IsInvalidGenLoc8Other(int memory, int variable)
{
if (variable > byte.MaxValue)
return true;
var arg = (byte)variable;
return memory switch
{
_ => !PossibleGeneralLocations8.Contains(arg),
};
}
public override bool CanHaveIntensity(int memory, int intensity) => true; // todo
public override bool CanHaveFeeling(int memory, int feeling) => true; // todo
public override int GetMinimumIntensity(int memory) => 3; // todo
}
}

View file

@ -0,0 +1,230 @@
using System.Collections.Generic;
namespace PKHeX.Core
{
public partial class MemoryContext8
{
private static readonly int[] Memory_NotSWSH =
{
// There's probably more. Send a pull request!
20, // {0} surfed across the water, carrying {1} on its back. {4} that {3}.
24, // {0} flew, carrying {1} on its back, to {2}. {4} that {3}.
35, // {0} proudly used Strength at {1}s instruction in... {2}. {4} that {3}.
36, // {0} proudly used Cut at {1}s instruction in... {2}. {4} that {3}.
37, // {0} shattered rocks to its hearts content at {1}s instruction in... {2}. {4} that {3}.
38, // {0} used Waterfall while carrying {1} on its back in... {2}. {4} that {3}.
69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}.
};
/// <summary>
/// Galar locations with a Pokémon Center
/// </summary>
private static readonly int[] LocationsWithPokeCenter_SWSH =
{
14, 20, 34, 44, 56, 70, 78, 96, 102, 110,
};
private static readonly Dictionary<int, ushort[]> KeyItemMemoryArgsGen8 = new()
{
{(int) Species.Rotom, new ushort[] {1278}}, // Rotom Catalog
{(int) Species.Kyurem, new ushort[] {628, 629}}, // DNA Splicers
{(int) Species.Necrozma, new ushort[] {943, 944, 945, 946}}, // N-Lunarizer / N-Solarizer
{(int) Species.Calyrex, new ushort[] {1590, 1591}}, // Reigns of Unity
};
private static readonly HashSet<ushort> PurchaseableItemSWSH = new(Legal.TR_SWSH)
{
0002, 0003, 0004, 0006, 0007, 0008, 0009, 0010, 0011, 0013,
0014, 0015, 0017, 0018, 0019, 0020, 0021, 0022, 0023, 0024,
0025, 0026, 0027, 0028, 0033, 0034, 0035, 0036, 0037, 0042,
0045, 0046, 0047, 0048, 0049, 0050, 0051, 0052, 0055, 0056,
0057, 0058, 0059, 0060, 0061, 0062, 0063, 0076, 0077, 0079,
0089, 0149, 0150, 0151, 0155, 0214, 0215, 0219, 0220, 0254,
0255, 0269, 0270, 0271, 0275, 0280, 0287, 0289, 0290, 0291,
0292, 0293, 0294, 0297, 0314, 0315, 0316, 0317, 0318, 0319,
0320, 0321, 0325, 0326, 0541, 0542, 0545, 0546, 0547, 0639,
0640, 0645, 0646, 0647, 0648, 0649, 0795, 0846, 0879, 1084,
1087, 1088, 1089, 1090, 1091, 1094, 1095, 1097, 1098, 1099,
1118, 1119, 1121, 1122, 1231, 1232, 1233, 1234, 1235, 1236,
1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246,
1247, 1248, 1249, 1250, 1251, 1252, 1256, 1257, 1258, 1259,
1260, 1261, 1262, 1263,
};
// {met, values allowed}
private static readonly Dictionary<byte, byte[]> MultiGenLocAreas = new()
{
// town of Postwick
// first town, at home, friend's house
{6, new byte[] {1, 2, 3}},
// town of Wedgehurst
// someone's house, boutique, simple town, Pokémon Center
{14, new byte[] {4, 6, 8, 9}},
// Route 2
// lab, tranquil road, lakeside road
{18, new byte[] {16, 44, 71}},
// city of Motostoke
// boutique, Pokémon Center, hotel, Pokémon Gym, stylish café, hair salon, town with a mysterious air
{20, new byte[] {6, 9, 11, 20, 24, 30, 35}},
// Motostoke Stadium
// Pokémon Gym, stadium
{24, new byte[] {20, 73}},
// town of Turffield
// Pokémon Center, town with a mysterious air
{34, new byte[] {9, 12}},
// Route 5
// tranquil road, Pokémon Nursery
{40, new byte[] {44, 74}},
// town of Hulbury
// someones house, Pokémon Center, restaurant, seaside town
{44, new byte[] {4, 9, 31, 33}},
// city of Hammerlocke
// someones house, boutique, Pokémon Center, Pokémon Gym, stylish café, hair salon, town with a mysterious air
{56, new byte[] {4, 6, 9, 20, 24, 30, 35}},
// town of Stow-on-Side
// someones house, Pokémon Center, town in the mountains
{70, new byte[] {4, 9, 76}},
// town of Ballonlea
// someones house, Pokémon Center, town with a mysterious air
{78, new byte[] {4, 9, 12}},
// town of Circhester
// someones house, boutique, Pokémon Center, hotel, hair salon, restaurant, snowcapped town
{96, new byte[] {4, 6, 9, 11, 30, 31, 37}},
// town of Spikemuth
// Pokémon Center, run-down town
{102, new byte[] {9, 77}},
// city of Wyndon
// someones house, boutique, Pokémon Center, hotel, large town, stylish café, hair salon
{110, new byte[] {4, 6, 9, 11, 22, 24, 30}},
// town of Freezington
// someones house, snowcapped town
{206, new byte[] {04, 37}},
};
// {met, value allowed}
private static readonly Dictionary<byte, byte> SingleGenLocAreas = new()
{
{008, 41}, // Slumbering Weald, forest
{012, 44}, // Route 1, tranquil road
{016, 28}, // Wedgehurst Station, train station
{022, 28}, // Motostoke Station, train station
{028, 44}, // Route 3, tranquil road
{030, 75}, // Galar Mine, mine
{032, 44}, // Route 4, tranquil road
{036, 73}, // Turffield Stadium, stadium
{046, 28}, // Hulbury Station, train station
{048, 73}, // Hulbury Stadium, stadium
{052, 35}, // Motostoke Outskirts, town with a mysterious air
{054, 75}, // Galar Mine No. 2, mine
{058, 28}, // Hammerlocke Station, train station
{060, 73}, // Hammerlocke Stadium, stadium
{064, 79}, // Energy Plant, dangerous place
{066, 79}, // the tower summit, dangerous place
{068, 47}, // Route 6, rugged mountain pass
{072, 73}, // Stow-on-Side Stadium, stadium
{076, 41}, // Glimwood Tangle, forest
{080, 73}, // Ballonlea Stadium, stadium
{084, 44}, // Route 7, tranquil road
{086, 47}, // Route 8, rugged mountain pass
{088, 53}, // Route 8 (on Steamdrift Way), snow-swept road
{090, 53}, // Route 9, snow-swept road
{092, 53}, // Route 9 (in Circhester Bay), snow-swept road
{094, 53}, // Route 9 (in Outer Spikemuth), snow-swept road
{098, 73}, // Circhester Stadium, stadium
{104, 78}, // Route 9 Tunnel, tunnel
{106, 53}, // Route 10, snow-swept road
{108, 28}, // White Hill Station, train station
{112, 28}, // Wyndon Station, train station
{114, 38}, // Wyndon Stadium (at the Pokémon League HQ), Pokémon League
{116, 38}, // Wyndon Stadium (in a locker room), Pokémon League
{120, 44}, // Meetup Spot, tranquil road
{122, 72}, // Rolling Fields, vast field
{124, 72}, // Dappled Grove, vast field
{126, 72}, // Watchtower Ruins, vast field
{128, 72}, // East Lake Axewell, vast field
{130, 72}, // West Lake Axewell, vast field
{132, 72}, // Axews Eye, vast field
{134, 72}, // South Lake Miloch, vast field
{136, 72}, // near the Giants Seat, vast field
{138, 72}, // North Lake Miloch, vast field
{140, 72}, // Motostoke Riverbank, vast field
{142, 72}, // Bridge Field, vast field
{144, 72}, // Stony Wilderness, vast field
{146, 72}, // Dusty Bowl, vast field
{148, 72}, // around the Giants Mirror, vast field
{150, 72}, // the Hammerlocke Hills, vast field
{152, 72}, // near the Giants Cap, vast field
{154, 72}, // Lake of Outrage, vast field
{156, 28}, // Wild Area Station, train station
{158, 34}, // Battle Tower, tall building
{160, 34}, // Rose Tower, tall building
{164, 72}, // Fields of Honor, vast field
{166, 50}, // Soothing Wetlands, muddy road
{168, 41}, // Forest of Focus, forest
{170, 49}, // Challenge Beach, seaside road
{172, 40}, // Brawlers Cave, cave
{174, 76}, // Challenge Road, town in the mountains
{176, 40}, // Courageous Cavern, cave
{178, 49}, // Loop Lagoon, seaside road
{180, 72}, // Training Lowlands, vast field
{182, 40}, // Warm-Up Tunnel, cave
{184, 51}, // Potbottom Desert, sand-swept road
{186, 49}, // Workout Sea, seaside road
{188, 49}, // Stepping-Stone Sea, seaside road
{190, 49}, // Insular Sea, seaside road
{192, 49}, // Honeycalm Sea, seaside road
{194, 49}, // Honeycalm Island, seaside road
{196, 29}, // Master Dojo, battling spot
{198, 34}, // Tower of Darkness, tall building
{200, 34}, // Tower of Waters, tall building
{202, 28}, // Armor Station, train station
{204, 53}, // Slippery Slope, snow-swept road
{208, 53}, // Frostpoint Field, snow-swept road
{210, 72}, // Giants Bed, vast field
{212, 48}, // Old Cemetery, stone-lined area
{214, 53}, // Snowslide Slope, snow-swept road
{216, 40}, // Tunnel to the Top, cave
{218, 53}, // the Path to the Peak, snow-swept road
{220, 65}, // Crown Shrine, mystical place
{222, 44}, // Giants Foot, tranquil road
{224, 40}, // Roaring-Sea Caves, cave
{226, 49}, // Frigid Sea, seaside road
{228, 72}, // Three-Point Pass, vast field
{230, 72}, // Ballimere Lake, vast field
{232, 40}, // Lakeside Cave, cave
{234, 72}, // Dyna Tree Hill, vast field
{236, 65}, // Rock Peak Ruins, mystical place
{238, 65}, // Iceberg Ruins, mystical place
{240, 65}, // Iron Ruins, mystical place
{242, 65}, // Split-Decision Ruins, mystical place
{244, 40}, // Max Lair, cave
{246, 28}, // Crown Tundra Station, train station
};
private static readonly HashSet<byte> PossibleGeneralLocations8 = new()
{
01, 02, 03, 04, 06, 08, 09,
11, 12, 16,
20, 22, 24, 28, 29,
30, 31, 33, 34, 35, 37, 38,
40, 41, 44, 47, 48, 49,
50, 51, 53,
65,
71, 72, 73, 74, 75, 76, 77, 78, 79,
};
}
}

View file

@ -17,30 +17,26 @@ namespace PKHeX.Core
public static bool CanWinRotoLoto(int generation, int item) public static bool CanWinRotoLoto(int generation, int item)
{ {
return true; // todo var context = Memories.GetContext(generation);
return context.CanWinRotoLoto(item);
} }
public static bool CanHoldItem(int generation, int item) public static bool CanHoldItem(int generation, int item)
{ {
return true; // todo var context = Memories.GetContext(generation);
return context.CanHoldItem(item);
} }
public static bool CanPlantBerry(int generation, int item) public static bool CanPlantBerry(int generation, int item)
{ {
return true; // todo var context = Memories.GetContext(generation);
return context.CanPlantBerry(item);
} }
public static bool CanUseItemGeneric(int generation, int item) public static bool CanUseItemGeneric(int generation, int item)
{ {
if (generation == 6) var context = Memories.GetContext(generation);
{ return context.CanUseItemGeneric(item);
// Key Item usage while in party on another species.
if (Memories.KeyItemUsableObserve6.Contains((ushort) item))
return true;
if (Memories.KeyItemMemoryArgsGen6.Values.Any(z => z.Contains((ushort) item)))
return true;
}
return true; // todo
} }
public static bool CanUseItem(int generation, int item, int species) public static bool CanUseItem(int generation, int item, int species)
@ -52,47 +48,22 @@ namespace PKHeX.Core
return true; // todo return true; // todo
} }
private static bool IsUsedKeyItemUnspecific(int generation, int item) => generation switch private static bool IsUsedKeyItemUnspecific(int generation, int item)
{ {
6 => Memories.KeyItemUsableObserve6.Contains((ushort)item), var context = Memories.GetContext(generation);
_ => false, return context.IsUsedKeyItemUnspecific(item);
}; }
private static bool IsUsedKeyItemSpecific(int generation, int item, int species) => generation switch private static bool IsUsedKeyItemSpecific(int generation, int item, int species)
{ {
6 => Memories.KeyItemMemoryArgsGen6.TryGetValue(species, out var value) && value.Contains((ushort) item), var context = Memories.GetContext(generation);
8 => Memories.KeyItemMemoryArgsGen8.TryGetValue(species, out var value) && value.Contains((ushort) item), return context.IsUsedKeyItemSpecific(item, species);
_ => false, }
};
public static bool CanBuyItem(int generation, int item, GameVersion version = GameVersion.Any) public static bool CanBuyItem(int generation, int item, GameVersion version = GameVersion.Any)
{ {
return generation switch var context = Memories.GetContext(generation);
{ return context.CanBuyItem(item, version);
6 => CanBuyItem6(item, version),
8 => CanBuyItem8(item, version),
_ => true, // todo
};
}
private static bool CanBuyItem6(int item, GameVersion version)
{
if (version is GameVersion.Any)
return PurchaseableItemAO.Contains((ushort) item) || PurchaseableItemXY.Contains((ushort) item);
if (version is GameVersion.X or GameVersion.Y)
return PurchaseableItemXY.Contains((ushort)item);
if (version is GameVersion.AS or GameVersion.OR)
return PurchaseableItemAO.Contains((ushort)item);
return false;
}
private static bool CanBuyItem8(int item, GameVersion version)
{
if (item is 1085) // Bob's Food Tin
return version is GameVersion.SW or GameVersion.Any;
if (item is 1086) // Bach's Food Tin
return version is GameVersion.SH or GameVersion.Any;
return PurchaseableItemSWSH.Contains((ushort) item);
} }
public static bool GetCanLearnMachineMove(PKM pkm, int move, int generation, GameVersion version = GameVersion.Any) public static bool GetCanLearnMachineMove(PKM pkm, int move, int generation, GameVersion version = GameVersion.Any)
@ -261,49 +232,5 @@ namespace PKHeX.Core
(int)Duraludon, (int)Duraludon,
(int)Urshifu, (int)Urshifu,
}; };
private static readonly HashSet<ushort> PurchaseableItemXY = new()
{
002, 003, 004, 006, 007, 008, 009, 010, 011, 012,
013, 014, 015, 017, 018, 019, 020, 021, 022, 023,
024, 025, 026, 027, 028, 034, 035, 036, 037, 045,
046, 047, 048, 049, 052, 055, 056, 057, 058, 059,
060, 061, 062, 076, 077, 078, 079, 082, 084, 085,
254, 255, 314, 315, 316, 317, 318, 319, 320, 334,
338, 341, 342, 343, 345, 347, 352, 355, 360, 364,
365, 377, 379, 395, 402, 403, 405, 411, 618,
};
private static readonly HashSet<ushort> PurchaseableItemAO = new()
{
002, 003, 004, 006, 007, 008, 009, 010, 011, 013,
014, 015, 017, 018, 019, 020, 021, 022, 023, 024,
025, 026, 027, 028, 034, 035, 036, 037, 045, 046,
047, 048, 049, 052, 055, 056, 057, 058, 059, 060,
061, 062, 063, 064, 076, 077, 078, 079, 254, 255,
314, 315, 316, 317, 318, 319, 320, 328, 336, 341,
342, 343, 344, 347, 352, 360, 365, 367, 369, 374,
379, 384, 395, 398, 400, 403, 405, 409, 577, 692,
694,
};
internal static readonly HashSet<ushort> PurchaseableItemSWSH = new(Legal.TR_SWSH)
{
0002, 0003, 0004, 0006, 0007, 0008, 0009, 0010, 0011, 0013,
0014, 0015, 0017, 0018, 0019, 0020, 0021, 0022, 0023, 0024,
0025, 0026, 0027, 0028, 0033, 0034, 0035, 0036, 0037, 0042,
0045, 0046, 0047, 0048, 0049, 0050, 0051, 0052, 0055, 0056,
0057, 0058, 0059, 0060, 0061, 0062, 0063, 0076, 0077, 0079,
0089, 0149, 0150, 0151, 0155, 0214, 0215, 0219, 0220, 0254,
0255, 0269, 0270, 0271, 0275, 0280, 0287, 0289, 0290, 0291,
0292, 0293, 0294, 0297, 0314, 0315, 0316, 0317, 0318, 0319,
0320, 0321, 0325, 0326, 0541, 0542, 0545, 0546, 0547, 0639,
0640, 0645, 0646, 0647, 0648, 0649, 0795, 0846, 0879, 1084,
1087, 1088, 1089, 1090, 1091, 1094, 1095, 1097, 1098, 1099,
1118, 1119, 1121, 1122, 1231, 1232, 1233, 1234, 1235, 1236,
1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246,
1247, 1248, 1249, 1250, 1251, 1252, 1256, 1257, 1258, 1259,
1260, 1261, 1262, 1263,
};
} }
} }

View file

@ -0,0 +1,39 @@
namespace PKHeX.Core
{
public readonly struct MemoryVariableSet
{
public readonly string Handler;
public readonly int MemoryID;
public readonly int Variable;
public readonly int Intensity;
public readonly int Feeling;
private MemoryVariableSet(string handler, int m, int v, int i, int f)
{
Handler = handler;
MemoryID = m;
Variable = v;
Intensity = i;
Feeling = f;
}
public static MemoryVariableSet Read(ITrainerMemories pkm, int handler) => handler switch
{
0 => new MemoryVariableSet(LegalityCheckStrings.L_XOT, pkm.OT_Memory, pkm.OT_TextVar, pkm.OT_Intensity, pkm.OT_Feeling), // OT
1 => new MemoryVariableSet(LegalityCheckStrings.L_XHT, pkm.HT_Memory, pkm.HT_TextVar, pkm.HT_Intensity, pkm.HT_Feeling), // HT
_ => new MemoryVariableSet(LegalityCheckStrings.L_XOT, 0, 0, 0, 0),
};
public bool Equals(MemoryVariableSet v) => v.Handler == Handler
&& v.MemoryID == MemoryID
&& v.Variable == Variable
&& v.Intensity == Intensity
&& v.Feeling == Feeling;
public override bool Equals(object obj) => obj is MemoryVariableSet v && Equals(v);
public override int GetHashCode() => -1;
public static bool operator ==(MemoryVariableSet left, MemoryVariableSet right) => left.Equals(right);
public static bool operator !=(MemoryVariableSet left, MemoryVariableSet right) => !(left == right);
}
}

View file

@ -18,27 +18,29 @@ namespace PKHeX.Core
VerifyHTMemory(data); VerifyHTMemory(data);
} }
private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo info) private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo info, MemoryContext context)
{ {
var memory = MemoryVariableSet.Read((ITrainerMemories)pkm, handler); var memory = MemoryVariableSet.Read((ITrainerMemories)pkm, handler);
// Actionable HM moves // Actionable HM moves
int matchingMoveMemory = Array.IndexOf(Memories.MoveSpecificMemories[0], memory.MemoryID); int matchingMoveMemory = Array.IndexOf(MemoryContext6.MoveSpecificMemories[0], memory.MemoryID);
if (matchingMoveMemory != -1) if (matchingMoveMemory != -1)
{ {
// Gen8 has no HMs, so this memory can never exist. if (gen != 6) // Gen8 has no HMs, so this memory can never exist.
if (gen != 6 || (pkm.Species != (int)Species.Smeargle && !GetCanLearnMachineMove(pkm, info.EvoChainsAllGens[gen], Memories.MoveSpecificMemories[1][matchingMoveMemory], 6))) return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler));
if (pkm.Species != (int)Species.Smeargle)
{
if (!GetCanLearnMachineMove(pkm, info.EvoChainsAllGens[gen], MemoryContext6.MoveSpecificMemories[1][matchingMoveMemory], 6))
return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler));
} }
}
if (gen == 8 && Memories.MemoryGeneral.Contains(memory.MemoryID) && Memories.IsInvalidGenLoc8Other(memory.MemoryID, memory.Variable)) if (context.IsInvalidGeneralLocationMemoryValue(memory.MemoryID, memory.Variable, info.EncounterMatch, pkm))
return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler)); return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler));
switch (memory.MemoryID) switch (memory.MemoryID)
{ {
case 1 or 2 or 3 when gen == 8 && Memories.IsInvalidGenLoc8(memory.MemoryID, pkm.Met_Location, pkm.Egg_Location, memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler));
case 19 when pkm.Species is (int)Species.Urshifu && memory.Variable is not 34: // tall building is the only location for evolving Urshifu case 19 when pkm.Species is (int)Species.Urshifu && memory.Variable is not 34: // tall building is the only location for evolving Urshifu
case 19 when pkm.Species is (int)Species.Runerigus && memory.Variable is not 72: // vast field is the only location for evolving Runerigus case 19 when pkm.Species is (int)Species.Runerigus && memory.Variable is not 72: // vast field is the only location for evolving Runerigus
return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler)); return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler));
@ -94,20 +96,20 @@ namespace PKHeX.Core
return GetInvalid(string.Format(LMemoryArgBadItem, memory.Handler)); return GetInvalid(string.Format(LMemoryArgBadItem, memory.Handler));
} }
if (gen == 6 && !Memories.CanHaveIntensity(memory.MemoryID, memory.Intensity)) return VerifyCommonMemoryEtc(memory, context);
{
var encGen = info.EncounterMatch.Generation;
if (encGen == 6 || (encGen == 7 && memory.MemoryID != 0)) // todo: memory intensity checks for gen8
return GetInvalid(string.Format(LMemoryIndexIntensityMin, memory.Handler, Memories.GetMinimumIntensity(memory.MemoryID)));
} }
if (gen == 6 && memory.MemoryID != 4 && !Memories.CanHaveFeeling(memory.MemoryID, memory.Feeling)) private CheckResult VerifyCommonMemoryEtc(MemoryVariableSet memory, MemoryContext context)
{ {
var encGen = info.EncounterMatch.Generation; if (!context.CanHaveIntensity(memory.MemoryID, memory.Intensity))
if (encGen == 6 || (encGen == 7 && memory.MemoryID != 0)) // todo: memory feeling checks for gen8 {
return GetInvalid(string.Format(LMemoryFeelInvalid, memory.Handler)); var min = context.GetMinimumIntensity(memory.MemoryID);
return GetInvalid(string.Format(LMemoryIndexIntensityMin, memory.Handler, min));
} }
if (!context.CanHaveFeeling(memory.MemoryID, memory.Feeling))
return GetInvalid(string.Format(LMemoryFeelInvalid, memory.Handler));
return GetValid(string.Format(LMemoryF_0_Valid, memory.Handler)); return GetValid(string.Format(LMemoryF_0_Valid, memory.Handler));
} }
@ -182,14 +184,9 @@ namespace PKHeX.Core
} }
// Bounds checking // Bounds checking
switch (memoryGen) var context = Memories.GetContext(memoryGen);
{ if (!context.CanObtainMemoryOT((GameVersion)pkm.Version, memory))
case 6 when pkm.XY && (memory > Memories.MAX_MEMORY_ID_XY || Memories.Memory_NotXY.Contains(memory)):
case 6 when pkm.AO && (memory > Memories.MAX_MEMORY_ID_AO || Memories.Memory_NotAO.Contains(memory)):
case 8 when pkm.SWSH && (memory > Memories.MAX_MEMORY_ID_SWSH || Memories.Memory_NotSWSH.Contains(memory)):
data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XOT))); data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XOT)));
break;
}
// Verify memory if specific to OT // Verify memory if specific to OT
switch (memory) switch (memory)
@ -211,12 +208,9 @@ namespace PKHeX.Core
return; return;
// {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}. // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
case 6 when memoryGen == 6 && !Memories.GetHasPokeCenterLocation((GameVersion)pkm.Version, mem.OT_TextVar): case 6 when !context.HasPokeCenter((GameVersion)pkm.Version, mem.OT_TextVar):
data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XOT))); data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XOT)));
return; return;
case 6 when memoryGen == 8 && mem.OT_TextVar != 0:
data.AddLine(Get(string.Format(LMemoryArgBadLocation, L_XOT), ParseSettings.Gen8MemoryLocationTextVariable));
return;
// {0} was with {1} when {1} caught {2}. {4} that {3}. // {0} was with {1} when {1} caught {2}. {4} that {3}.
case 14: case 14:
@ -227,7 +221,7 @@ namespace PKHeX.Core
return; return;
} }
data.AddLine(VerifyCommonMemory(pkm, 0, Info.Generation, Info)); data.AddLine(VerifyCommonMemory(pkm, 0, Info.Generation, Info, context));
} }
private static bool CanHaveMemoryForOT(PKM pkm, int origin, int memory) private static bool CanHaveMemoryForOT(PKM pkm, int origin, int memory)
@ -281,13 +275,9 @@ namespace PKHeX.Core
var memoryGen = pkm.Format >= 8 ? 8 : 6; var memoryGen = pkm.Format >= 8 ? 8 : 6;
// Bounds checking // Bounds checking
switch (memoryGen) var context = Memories.GetContext(memoryGen);
{ if (!context.CanObtainMemoryHT((GameVersion)pkm.Version, memory))
case 6 when memory > Memories.MAX_MEMORY_ID_AO:
case 8 when memory > Memories.MAX_MEMORY_ID_SWSH || Memories.Memory_NotSWSH.Contains(memory):
data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XHT))); data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XHT)));
break;
}
// Verify memory if specific to HT // Verify memory if specific to HT
switch (memory) switch (memory)
@ -309,12 +299,9 @@ namespace PKHeX.Core
return; return;
// {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}. // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
case 6 when memoryGen == 6 && !Memories.GetHasPokeCenterLocation(GameVersion.Gen6, mem.HT_TextVar): case 6 when !context.HasPokeCenter(GameVersion.Any, mem.HT_TextVar):
data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XHT))); data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XHT)));
return; return;
case 6 when memoryGen == 8 && mem.HT_TextVar != 0:
data.AddLine(Get(string.Format(LMemoryArgBadLocation, L_XHT), ParseSettings.Gen8MemoryLocationTextVariable));
return;
// {0} was with {1} when {1} caught {2}. {4} that {3}. // {0} was with {1} when {1} caught {2}. {4} that {3}.
case 14: case 14:
@ -325,7 +312,7 @@ namespace PKHeX.Core
return; return;
} }
var commonResult = VerifyCommonMemory(pkm, 1, memoryGen, Info); var commonResult = VerifyCommonMemory(pkm, 1, memoryGen, Info, context);
data.AddLine(commonResult); data.AddLine(commonResult);
} }

View file

@ -365,14 +365,14 @@ namespace PKHeX.Core
pk.OT_Memory = 3; pk.OT_Memory = 3;
pk.OT_TextVar = 9; pk.OT_TextVar = 9;
pk.OT_Intensity = 1; pk.OT_Intensity = 1;
pk.OT_Feeling = Memories.GetRandomFeeling(pk.OT_Memory, 10); // 0-9 pk.OT_Feeling = MemoryContext6.GetRandomFeeling6(pk.OT_Memory, 10); // 0-9
} }
else else
{ {
pk.HT_Memory = 3; pk.HT_Memory = 3;
pk.HT_TextVar = 9; pk.HT_TextVar = 9;
pk.HT_Intensity = 1; pk.HT_Intensity = 1;
pk.HT_Feeling = Memories.GetRandomFeeling(pk.HT_Memory, 10); // 0-9 pk.HT_Feeling = MemoryContext6.GetRandomFeeling6(pk.HT_Memory, 10); // 0-9
pk.HT_Friendship = pk.OT_Friendship; pk.HT_Friendship = pk.OT_Friendship;
} }
} }

View file

@ -446,7 +446,7 @@ namespace PKHeX.Core
HT_Gender = PKMConverter.OT_Gender, HT_Gender = PKMConverter.OT_Gender,
HT_Intensity = 1, HT_Intensity = 1,
HT_Memory = 4, HT_Memory = 4,
HT_Feeling = Memories.GetRandomFeeling(4), HT_Feeling = MemoryContext6.GetRandomFeeling6(4),
}; };
// Write Transfer Location - location is dependent on 3DS system that transfers. // Write Transfer Location - location is dependent on 3DS system that transfers.

View file

@ -21,7 +21,7 @@
ht.HT_Memory = 4; // Link trade to [VAR: General Location] ht.HT_Memory = 4; // Link trade to [VAR: General Location]
ht.HT_TextVar = bank ? 0 : 9; // Somewhere (Bank) : Pokécenter (Trade) ht.HT_TextVar = bank ? 0 : 9; // Somewhere (Bank) : Pokécenter (Trade)
ht.HT_Intensity = 1; ht.HT_Intensity = 1;
ht.HT_Feeling = Memories.GetRandomFeeling(4, bank ? 10 : 20); // 0-9 Bank, 0-19 Trade ht.HT_Feeling = MemoryContext6.GetRandomFeeling6(4, bank ? 10 : 20); // 0-9 Bank, 0-19 Trade
} }
/// <summary> /// <summary>