using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; namespace PKHeX.Core; /// /// Contains many instances to match against a . /// public sealed class TrainerDatabase { private readonly Dictionary> Database = []; /// /// Gets the number of unique versions in the database. /// public int CountVersions => Database.Count; /// /// Gets the number of trainers in the database. /// public int CountTrainers => Database.Sum(z => z.Value.Count); /// /// Checks if the database contains any trainers for the specified . /// /// public bool HasVersion(GameVersion version) => Database.ContainsKey(version); /// /// Gets all trainers from the database for the specified saved . /// /// Saved Version to fetch trainers for public ReadOnlySpan GetTrainers(GameVersion version) { if (Database.TryGetValue(version, out var list)) return CollectionsMarshal.AsSpan(list); return default; } /// /// Fetches an appropriate trainer based on the requested . /// /// Version the trainer should originate from /// Language to request for /// Null if no trainer found for this version. public ITrainerInfo? GetTrainer(GameVersion version, LanguageID? language = null) { if (version <= 0) return null; if (!version.IsValidSavedVersion()) return GetTrainerFromGroup(version, language); if (Database.TryGetValue(version, out var list)) return list[GetRandomIndex(list.Count)]; return null; } private static int GetRandomIndex(int count) => count == 1 ? 0 : Util.Rand.Next(count); /// /// Fetches an appropriate trainer based on the requested group. /// /// Version the trainer should originate from /// Language to request for /// Null if no trainer found for this version. private ITrainerInfo? GetTrainerFromGroup(GameVersion version, LanguageID? lang = null) { var possible = Database.Where(z => version.Contains(z.Key)).ToList(); if (possible.Count == 0) return null; if (lang is not null) { possible = possible.Select(z => { var filtered = z.Value.Where(x => x.Language == (int)lang).ToList(); return new KeyValuePair>(z.Key, filtered); }).Where(z => z.Value.Count != 0).ToList(); } var span = CollectionsMarshal.AsSpan(possible); return GetRandomTrainer(span); } /// /// Fetches an appropriate trainer based on the requested . /// /// Generation the trainer should inhabit /// Language to request for /// Null if no trainer found for this version. public ITrainerInfo? GetTrainerFromContext(EntityContext context, LanguageID? lang = null) { var possible = Database.Where(z => z.Key.Context == context).ToList(); if (possible.Count == 0) return null; if (lang is not null) { possible = possible.Select(z => { var filtered = z.Value.Where(x => x.Language == (int)lang).ToList(); return new KeyValuePair>(z.Key, filtered); }).Where(z => z.Value.Count != 0).ToList(); } var span = CollectionsMarshal.AsSpan(possible); return GetRandomTrainer(span); } private static ITrainerInfo? GetRandomTrainer(ReadOnlySpan>> possible) { if (possible.Length == 0) return null; var group = possible[GetRandomIndex(possible.Length)]; var span = group.Value; return span[GetRandomIndex(span.Count)]; } /// /// Adds the to the . /// /// Trainer details to add. public void Register(ITrainerInfo trainer) { var version = trainer.Version; if (!Database.TryGetValue(version, out var list)) { Database.Add(version, [trainer]); return; } if (list.Contains(trainer)) return; list.Add(trainer); } /// /// Adds the trainer details of the to the . /// /// Pokémon with Trainer details to add. /// A copy of the object will be made to prevent modifications, just in case. public void RegisterCopy(PKM pk) => Register(GetTrainerReference(pk)); /// /// Adds the trainer details of the to the . /// /// Pokémon with Trainer details to add. /// A copy of the object will be made to prevent modifications, just in case. public void RegisterCopy(ITrainerInfo info) => Register(new SimpleTrainerInfo(info)); private static SimpleTrainerInfo GetTrainerReference(PKM pk) { var (cr, c, r) = GetRegion3DS(pk); return GetTrainerReference(pk, cr, c, r); } private static SimpleTrainerInfo GetTrainerReference(PKM pk, byte cr, byte c, byte r) => new(pk.Version) { TID16 = pk.TID16, SID16 = pk.SID16, OT = pk.OriginalTrainerName, Gender = pk.OriginalTrainerGender, Language = pk.Language, Generation = pk.Generation, ConsoleRegion = cr, Country = c, Region = r, }; private static (byte ConsoleRegion, byte Country, byte Region) GetRegion3DS(PKM pk) { if (pk is IRegionOriginReadOnly x) return (x.ConsoleRegion, x.Country, x.Region); if (pk.Version.IsGen6() || pk.Version.IsGen7()) { if (pk.Language == (int)LanguageID.Japanese) return (0, 1, 0); return (1, 7, 49); } return default; } /// /// Clears all trainer details from the . /// public void Clear() => Database.Clear(); }