using System; using System.Collections.Generic; using System.Linq; using static PKHeX.Core.Legal; namespace PKHeX.Core { public static class MoveTutor { public static GameVersion GetIsTutorMove(PKM pkm, int species, int form, int generation, int move, bool specialTutors = true) { return generation switch { 1 => GetIsTutor1(pkm, species, move), 2 => GetIsTutor2(pkm, species, move), 3 => GetIsTutor3(species, move), 4 => GetIsTutor4(species, form, move), 5 => GetIsTutor5(pkm, species, form, specialTutors, move), 6 => GetIsTutor6(pkm, species, form, specialTutors, move), 7 => GetIsTutor7(pkm, species, form, specialTutors, move), 8 => GetIsTutor8(pkm, species, form, specialTutors, move), _ => NONE, }; } private static GameVersion GetIsTutor1(PKM pkm, int species, int move) { // Surf Pikachu via Stadium if (move != (int)Move.Surf || ParseSettings.AllowGBCartEra) return NONE; if (pkm.Format < 3 && species is (int)Species.Pikachu or (int)Species.Raichu) return GameVersion.Stadium; return NONE; } private static GameVersion GetIsTutor2(PKM pkm, int species, int move) { if (!ParseSettings.AllowGen2Crystal(pkm)) return NONE; var info = PersonalTable.C[species]; var tutor = Array.IndexOf(Tutors_GSC, move); if (tutor != -1 && info.TMHM[57 + tutor]) return GameVersion.C; return NONE; } private static GameVersion GetIsTutor3(int species, int move) { // E Tutors (Free) // E Tutors (BP) var info = PersonalTable.E[species]; var e = Array.IndexOf(Tutor_E, move); if (e != -1 && info.TypeTutors[e]) return GameVersion.E; // FRLG Tutors // Only special tutor moves, normal tutor moves are already included in Emerald data var frlg = Array.IndexOf(SpecialTutors_FRLG, move); if (frlg != -1 && SpecialTutors_Compatibility_FRLG[frlg] == species) return GameVersion.FRLG; // XD var xd = Array.IndexOf(SpecialTutors_XD_Exclusive, move); if (xd != -1 && SpecialTutors_Compatibility_XD_Exclusive[xd].Contains(species)) return GameVersion.XD; // XD (Mew) if (species == (int)Species.Mew && Tutor_3Mew.Contains(move)) return GameVersion.XD; return NONE; } private static GameVersion GetIsTutor4(int species, int form, int move) { var pi = PersonalTable.HGSS.GetFormEntry(species, form); var type = Array.IndexOf(Tutors_4, move); if (type != -1 && pi.TypeTutors[type]) return GameVersion.Gen4; var special = Array.IndexOf(SpecialTutors_4, move); if (special != -1 && SpecialTutors_Compatibility_4[special].Contains(species)) return GameVersion.HGSS; return NONE; } private static GameVersion GetIsTutor5(PKM pkm, int species, int form, bool specialTutors, int move) { var pi = PersonalTable.B2W2.GetFormEntry(species, form); var type = Array.IndexOf(TypeTutor6, move); if (type != -1 && pi.TypeTutors[type]) return GameVersion.Gen5; if (specialTutors && pkm.HasVisitedB2W2(species)) { var tutors = Tutors_B2W2; for (int i = 0; i < tutors.Length; i++) { var tutor = Array.IndexOf(tutors[i], move); if (tutor == -1) continue; if (pi.SpecialTutors[i][tutor]) return GameVersion.B2W2; break; } } return NONE; } private static GameVersion GetIsTutor6(PKM pkm, int species, int form, bool specialTutors, int move) { var pi = PersonalTable.AO.GetFormEntry(species, form); var type = Array.IndexOf(TypeTutor6, move); if (type != -1 && pi.TypeTutors[type]) return GameVersion.Gen6; if (specialTutors && pkm.HasVisitedORAS(species)) { var tutors = Tutors_AO; for (int i = 0; i < tutors.Length; i++) { var tutor = Array.IndexOf(tutors[i], move); if (tutor == -1) continue; if (pi.SpecialTutors[i][tutor]) return GameVersion.ORAS; break; } } return NONE; } private static GameVersion GetIsTutor7(PKM pkm, int species, int form, bool specialTutors, int move) { var pi = PersonalTable.USUM.GetFormEntry(species, form); var type = Array.IndexOf(TypeTutor6, move); if (type != -1 && pi.TypeTutors[type]) return GameVersion.Gen7; if (specialTutors && pkm.HasVisitedUSUM(species)) { var tutor = Array.IndexOf(Tutors_USUM, move); if (tutor != -1 && pi.SpecialTutors[0][tutor]) return GameVersion.USUM; } return NONE; } private static GameVersion GetIsTutor8(PKM pkm, int species, int form, bool specialTutors, int move) { if (pkm is PA8) { var pi = (PersonalInfoLA)PersonalTable.LA.GetFormEntry(species, form); if (!pi.IsPresentInGame) return NONE; var index = Array.IndexOf(MoveShop8_LA, (ushort)move); if (index != -1 && pi.SpecialTutors[0][index]) return GameVersion.PLA; return NONE; } if (pkm is PB8) { var pi = (PersonalInfoBDSP)PersonalTable.BDSP.GetFormEntry(species, form); if (!pi.IsPresentInGame) return NONE; var type = Array.IndexOf(TypeTutor8b, move); if (type != -1 && pi.TypeTutors[type]) return GameVersion.BDSP; return NONE; } else { var pi = (PersonalInfoSWSH)PersonalTable.SWSH.GetFormEntry(species, form); if (!pi.IsPresentInGame) return NONE; var type = Array.IndexOf(TypeTutor8, move); if (type != -1 && pi.TypeTutors[type]) return GameVersion.SWSH; if (!specialTutors) return NONE; var tutor = Array.IndexOf(Tutors_SWSH_1, move); if (tutor != -1 && pi.SpecialTutors[0][tutor]) return GameVersion.SWSH; return NONE; } } public static IEnumerable GetTutorMoves(PKM pkm, int species, int form, bool specialTutors, int generation) { List moves = new(); switch (generation) { case 1: AddMovesTutor1(moves, species, pkm.Format); break; case 2: AddMovesTutor2(moves, species, pkm.Format, pkm.Korean); break; case 3: AddMovesTutor3(moves, species); break; case 4: AddMovesTutor4(moves, species, form); break; case 5: AddMovesTutor5(moves, species, form, pkm, specialTutors); break; case 6: AddMovesTutor6(moves, species, form, pkm, specialTutors); break; case 7: AddMovesTutor7(moves, species, form, pkm, specialTutors); break; case 8: AddMovesTutor8(moves, species, form, pkm, specialTutors); break; } return moves.Distinct(); } private static void AddMovesTutor1(List moves, int species, int format) { if (ParseSettings.AllowGBCartEra && format < 3 && species is (int)Species.Pikachu or (int)Species.Raichu) // Surf Pikachu via Stadium moves.Add(57); } private static void AddMovesTutor2(List moves, int species, int format, bool korean = false) { if (korean) return; var pi = PersonalTable.C[species]; moves.AddRange(Tutors_GSC.Where((_, i) => pi.TMHM[57 + i])); AddMovesTutor1(moves, species, format); } private static void AddMovesTutor3(List moves, int species) { // E Tutors (Free) // E Tutors (BP) var pi = PersonalTable.E[species]; moves.AddRange(Tutor_E.Where((_, i) => pi.TypeTutors[i])); // FRLG Tutors // Only special tutor moves, normal tutor moves are already included in Emerald data moves.AddRange(SpecialTutors_FRLG.Where((_, i) => SpecialTutors_Compatibility_FRLG[i] == species)); // XD moves.AddRange(SpecialTutors_XD_Exclusive.Where((_, i) => SpecialTutors_Compatibility_XD_Exclusive[i].Any(e => e == species))); // XD (Mew) if (species == (int)Species.Mew) moves.AddRange(Tutor_3Mew); } private static void AddMovesTutor4(List moves, int species, int form) { var pi = PersonalTable.HGSS.GetFormEntry(species, form); moves.AddRange(Tutors_4.Where((_, i) => pi.TypeTutors[i])); moves.AddRange(SpecialTutors_4.Where((_, i) => SpecialTutors_Compatibility_4[i].Any(e => e == species))); } private static void AddMovesTutor5(List moves, int species, int form, PKM pkm, bool specialTutors) { var pi = PersonalTable.B2W2[species]; moves.AddRange(TypeTutor6.Where((_, i) => pi.TypeTutors[i])); if (pkm.InhabitedGeneration(5) && specialTutors) moves.AddRange(GetTutors(PersonalTable.B2W2.GetFormEntry(species, form), Tutors_B2W2)); } private static void AddMovesTutor6(List moves, int species, int form, PKM pkm, bool specialTutors) { var pi = PersonalTable.AO[species]; moves.AddRange(TypeTutor6.Where((_, i) => pi.TypeTutors[i])); if (specialTutors && pkm.HasVisitedORAS(species)) moves.AddRange(GetTutors(PersonalTable.AO.GetFormEntry(species, form), Tutors_AO)); } private static void AddMovesTutor7(List moves, int species, int form, PKM pkm, bool specialTutors) { if (pkm.Version is (int)GameVersion.GO or (int)GameVersion.GP or (int)GameVersion.GE) return; var pi = PersonalTable.USUM.GetFormEntry(species, form); moves.AddRange(TypeTutor6.Where((_, i) => pi.TypeTutors[i])); if (specialTutors && pkm.HasVisitedUSUM(species)) moves.AddRange(GetTutors(pi, Tutors_USUM)); } private static void AddMovesTutor8(List moves, int species, int form, PKM pkm, bool specialTutors) { if (pkm is PA8) { var pi = (PersonalInfoLA)PersonalTable.LA.GetFormEntry(species, form); if (!pi.IsPresentInGame) return; var shop = MoveShop8_LA; var tutors = pi.SpecialTutors[0]; for (int i = 0; i < shop.Length; i++) { if (tutors[i]) moves.Add(shop[i]); } return; } if (pkm is PB8) { var pi = (PersonalInfoBDSP)PersonalTable.BDSP.GetFormEntry(species, form); if (!pi.IsPresentInGame) return; moves.AddRange(TypeTutor8b.Where((_, i) => pi.TypeTutors[i])); } else // SWSH { var pi = (PersonalInfoSWSH)PersonalTable.SWSH.GetFormEntry(species, form); if (!pi.IsPresentInGame) return; moves.AddRange(TypeTutor8.Where((_, i) => pi.TypeTutors[i])); if (specialTutors) moves.AddRange(GetTutors(pi, Tutors_SWSH_1)); } } private static IEnumerable GetTutors(PersonalInfo pi, params int[][] tutors) { for (int i = 0; i < tutors.Length; i++) { for (int b = 0; b < tutors[i].Length; b++) { if (pi.SpecialTutors[i][b]) yield return tutors[i][b]; } } } internal static void AddSpecialTutorMoves(List r, PKM pkm, int Generation, int species) { switch (species) { case (int)Species.Keldeo: r.Add((int)Move.SecretSword); break; case (int)Species.Meloetta: r.Add((int)Move.RelicSong); break; case (int)Species.Pikachu when Generation == 7 && pkm.Form == 8: r.AddRange(Tutor_StarterPikachu); break; case (int)Species.Eevee when Generation == 7 && pkm.Form == 1: r.AddRange(Tutor_StarterEevee); break; case (int)Species.Pikachu or (int)Species.Raichu when Generation == 7 && !(pkm.LGPE || pkm.GO): r.Add((int)Move.VoltTackle); break; } } /// Rotom Moves that correspond to a specific form (form-0 ignored). private static readonly int[] RotomMoves = { (int)Move.Overheat, (int)Move.HydroPump, (int)Move.Blizzard, (int)Move.AirSlash, (int)Move.LeafStorm }; internal static void AddSpecialFormChangeMoves(List r, PKM pkm, int generation, int species) { switch (species) { case (int)Species.Rotom when generation >= 4: var formMoves = RotomMoves; var form = pkm.Form - 1; if ((uint)form < formMoves.Length) r.Add(RotomMoves[form]); break; case (int)Species.Zygarde when generation == 7: r.AddRange(ZygardeMoves); break; case (int)Species.Necrozma when pkm.Form == 1: // Sun r.Add((int)Move.SunsteelStrike); break; case (int)Species.Necrozma when pkm.Form == 2: // Moon r.Add((int)Move.MoongeistBeam); break; } } } }