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 != 57 || ParseSettings.AllowGBCartEra) return NONE; if (pkm.Format < 3 && (species == (int)Species.Pikachu || species == (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]; for (int i = 0; i < Tutors_GSC.Length; i++) { if (Tutors_GSC[i] == move) return info.TMHM[57 + i] ? GameVersion.C : NONE; } return GetIsTutor1(pkm, species, move); } private static GameVersion GetIsTutor3(int species, int move) { // E Tutors (Free) // E Tutors (BP) var info = PersonalTable.E[species]; for (int i = 0; i < Tutor_E.Length; i++) { if (Tutor_E[i] == move && info.TypeTutors[i]) return GameVersion.E; } // FRLG Tutors // Only special tutor moves, normal tutor moves are already included in Emerald data for (int i = 0; i < SpecialTutors_FRLG.Length; i++) { if (Tutor_FRLG[i] == move && species == SpecialTutors_Compatibility_FRLG[i]) return GameVersion.FRLG; } // XD for (int i = 0; i < SpecialTutors_XD_Exclusive.Length; i++) { if (SpecialTutors_XD_Exclusive[i] == move && SpecialTutors_Compatibility_XD_Exclusive[i].Any(e => e == 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); for (int i = 0; i < Tutors_4.Length; i++) { if (Tutors_4[i] == move && pi.TypeTutors[i]) return GameVersion.Gen4; } for (int i = 0; i < SpecialTutors_4.Length; i++) { if (SpecialTutors_4[i] == move && SpecialTutors_Compatibility_4[i].Any(e => e == 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 arr = TypeTutor6; for (int i = 0; i < arr.Length; i++) { if (arr[i] == move && pi.TypeTutors[i]) return GameVersion.Gen5; } if (specialTutors && pkm.HasVisitedB2W2(species)) { var tutors = Tutors_B2W2; for (int i = 0; i < tutors.Length; i++) { for (int j = 0; j < tutors[i].Length; j++) { if (tutors[i][j] == move && pi.SpecialTutors[i][j]) return GameVersion.B2W2; } } } return NONE; } private static GameVersion GetIsTutor6(PKM pkm, int species, int form, bool specialTutors, int move) { var pi = PersonalTable.AO.GetFormEntry(species, form); var arr = TypeTutor6; for (int i = 0; i < arr.Length; i++) { if (arr[i] == move && pi.TypeTutors[i]) return GameVersion.Gen6; } if (specialTutors && pkm.HasVisitedORAS(species)) { var tutors = Tutors_AO; for (int i = 0; i < tutors.Length; i++) { for (int j = 0; j < tutors[i].Length; j++) { if (tutors[i][j] == move && pi.SpecialTutors[i][j]) return GameVersion.ORAS; } } } return NONE; } private static GameVersion GetIsTutor7(PKM pkm, int species, int form, bool specialTutors, int move) { var pi = PersonalTable.USUM.GetFormEntry(species, form); var arr = TypeTutor6; for (int i = 0; i < arr.Length; i++) { if (arr[i] == move && pi.TypeTutors[i]) return GameVersion.Gen7; } if (specialTutors && pkm.HasVisitedUSUM(species)) { var tutors = Tutors_USUM; for (int i = 0; i < tutors.Length; i++) { if (tutors[i] == move && pi.SpecialTutors[0][i]) return GameVersion.USUM; } } return NONE; } private static GameVersion GetIsTutor8(PKM pkm, int species, int form, bool specialTutors, int move) { var pi = (PersonalInfoSWSH)PersonalTable.SWSH.GetFormEntry(species, form); var arr = TypeTutor8; for (int i = 0; i < arr.Length; i++) { if (arr[i] == move && pi.TypeTutors[i]) return GameVersion.Gen8; } if (!specialTutors) return NONE; var tutors = Tutors_SWSH_1; for (int i = 0; i < tutors.Length; i++) { if (tutors[i] == move && pi.SpecialTutors[0][i]) return GameVersion.USUM; } 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 == (int)Species.Pikachu || species == (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.GG) 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) { 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: // Keldeo r.Add(548); // Secret Sword break; case (int)Species.Meloetta: r.Add(547); // Relic Song break; case (int)Species.Pikachu when Generation == 6 && pkm.Format == 6: int index = pkm.Form - 1; if ((uint)index < CosplayPikachuMoves.Length) r.Add(CosplayPikachuMoves[index]); 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.GG: r.Add(344); // Volt Tackle break; } } /// Rotom Moves that correspond to a specific form (form-0 ignored). private static readonly int[] RotomMoves = { 315, 056, 059, 403, 437 }; 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(713); break; case (int)Species.Necrozma when pkm.Form == 2: // Moon r.Add(714); break; } } } }