using System; using System.Collections.Generic; using System.Linq; using pkNX.Structures; namespace pkNX.Randomization; public class MoveRandomizer : Randomizer { private readonly IReadOnlyList MoveData; private readonly IPersonalTable SpeciesStat; private readonly GameInfo Config; private GenericRandomizer RandMove; internal MovesetRandSettings Settings = new(); public MoveRandomizer(GameInfo config, IReadOnlyList moves, IPersonalTable t) { Config = config; var maxMoveId = config.MaxMoveID; MoveData = moves; SpeciesStat = t; RandMove = new GenericRandomizer(Enumerable.Range(1, maxMoveId - 1).ToArray()); } public override void Execute() => throw new Exception("Shouldn't be called."); public static ReadOnlySpan FixedDamageMoves => [49, 82]; public void Initialize(MovesetRandSettings settings, int[] bannedMoves) { Settings = settings; var banned = new List(); banned.AddRange(Legal.Taboo_Moves.Concat(Legal.Z_Moves).Concat(Legal.Max_Moves)); if (Settings.BanFixedDamageMoves) banned.AddRange(FixedDamageMoves); banned.AddRange(bannedMoves); Settings.BannedMoves = banned.ToArray(); var all = Enumerable.Range(1, Config.MaxMoveID - 1); var moves = all.Except(banned); RandMove = new GenericRandomizer(moves.ToArray()); } public int[] GetRandomLearnset(int index, int movecount) => GetRandomLearnset(SpeciesStat[index], movecount); public int[] GetRandomLearnset(IPersonalType Types, int movecount) { var oldSTABCount = Settings.STABCount; Settings.STABCount = (int)(Settings.STABPercent * movecount / 100); int[] moves = GetRandomMoveset(Types, movecount); Settings.STABCount = oldSTABCount; return moves; } public int[] GetRandomMoveset(int index, int movecount = 4) => GetRandomMoveset(SpeciesStat[index], movecount); public int[] GetRandomMoveset(IPersonalType Types, int movecount = 4) { int loopctr = 0; const int maxLoop = 666; int[] moves; do { moves = GetRandomMoves(Types, movecount); } while (!IsMovesetMeetingRequirements(moves, Types, movecount) && loopctr++ <= maxLoop); return moves; } private int[] GetRandomMoves(IPersonalType Types, int movecount = 4) { int[] moves = new int[movecount]; int i = 0; if (Settings.STAB) { for (; i < Settings.STABCount; i++) moves[i] = GetRandomSTABMove(Types); } for (; i < moves.Length; i++) // remainder of moves moves[i] = RandMove.Next(); return moves; } private int GetRandomSTABMove(IPersonalType types) { int move; int ctr = 0; do { move = RandMove.Next(); } while (!types.IsType((Types)MoveData[move].Type) && ctr++ < RandMove.Count); return move; } private bool IsMovesetMeetingRequirements(int[] moves, IPersonalType types, int count) { if (Settings.DMG && Settings.DMGCount > moves.Count(move => MoveData[move].Category != 0)) return false; if (Settings.STAB) { var stabCt = moves.Count(move => types.IsType((Types)MoveData[move].Type)); if (stabCt < Settings.STABCount) return false; } if (moves.Any(Settings.BannedMoves.Contains)) return false; return moves.Distinct().Count() == count; } public void ReorderMovesPower(IList moves) => ReorderMovesPower(moves, MoveData); private static void ReorderMovesPower(IList moves, IReadOnlyList movedata) { var data = moves.Select((Move, Index) => new { Index, Move, Data = movedata[Move] }); var powered = data.Where(z => z.Data.Power > 1).ToList(); var indexes = powered.ConvertAll(z => z.Index); var order = powered.OrderBy(z => z.Data.Power * Math.Max(1, (z.Data.HitMin + z.Data.HitMax) / 2m)).ToList(); for (var i = 0; i < order.Count; i++) moves[indexes[i]] = order[i].Move; } private static readonly int[] firstMoves = [ 1, // Pound 40, // Poison Sting 52, // Ember 55, // Water Gun 64, // Peck 71, // Absorb 84, // Thunder Shock 98, // Quick Attack 122, // Lick 141, // Leech Life ]; private static readonly GenericRandomizer first = new(firstMoves); public static int GetRandomFirstMoveAny() { first.Reset(); return first.Next(); } public int GetRandomFirstMove(int index) => GetRandomFirstMove(SpeciesStat[index]); public int GetRandomFirstMove(IPersonalType types) { first.Reset(); int ctr = 0; int move; do { move = first.Next(); if (++ctr == firstMoves.Length) return move; } while (!types.IsType((Types)MoveData[move].Type)); return move; } public bool SanitizeMovesetForBannedMoves(int[] moves, int index) { bool updated = false; for (int m = 0; m < moves.Length; m++) { if (!Settings.BannedMoves.Contains(moves[m])) continue; updated = true; moves[m] = GetRandomFirstMove(index); } return updated; } }