From b49c5a5a8afb76ad6cf683dca86776f90e9f6e11 Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 5 Dec 2019 22:32:47 -0800 Subject: [PATCH] Rewrite evolution lineage generating - Use tail recursion and a lookup table (species | form) for reversing - generate the reversal lookup by extrapolating all species-form destination values (EvolutionLink) All tests pass, and no special handling for forms is required. Kinda suspicious that it works this simply... Redo evolution banning (no wormhole for non-Alolan evolutions in SM), in a style that we can ban the Gigantamax event evolutions (pika/meowth/eevee). Their evolutions can't be resulted from a pre-evo, because the in-game routine early aborts if the gigantamax flag is set for those species. Update gen4 personal tables (formCount was 0, needs to be a minimum of 1 by default) Closes #2537 --- .../Legality/Evolutions/EvolutionLineage.cs | 109 ------ .../Legality/Evolutions/EvolutionMethod.cs | 32 +- .../Legality/Evolutions/EvolutionTree.cs | 338 ++++++++++-------- PKHeX.Core/Resources/byte/personal_dp | Bin 22044 -> 22044 bytes PKHeX.Core/Resources/byte/personal_hgss | Bin 22352 -> 22352 bytes PKHeX.Core/Resources/byte/personal_pt | Bin 22352 -> 22352 bytes 6 files changed, 196 insertions(+), 283 deletions(-) delete mode 100644 PKHeX.Core/Legality/Evolutions/EvolutionLineage.cs diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionLineage.cs b/PKHeX.Core/Legality/Evolutions/EvolutionLineage.cs deleted file mode 100644 index 018344d8d..000000000 --- a/PKHeX.Core/Legality/Evolutions/EvolutionLineage.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace PKHeX.Core -{ - /// - /// Information pertaining to a 's evolution lineage. - /// - public sealed class EvolutionLineage - { - public readonly List> Chain = new List>(); - - public void Insert(EvolutionMethod entry) - { - int matchChain = -1; - for (int i = 0; i < Chain.Count; i++) - { - if (Chain[i].Any(e => e.Species == entry.Species)) - matchChain = i; - } - - if (matchChain != -1) - Chain[matchChain].Add(entry); - else - Chain.Insert(0, new List {entry}); - } - - public List GetExplicitLineage(PKM pkm, int maxLevel, bool skipChecks, int maxSpeciesOrigin, int minLevel) - { - int lvl = maxLevel; - var first = new EvoCriteria {Species = pkm.Species, Level = lvl, Form = pkm.AltForm}; - var dl = new List(3) { first }; - for (int i = Chain.Count - 1; i >= 0; i--) // reverse evolution! - { - bool oneValid = false; - foreach (var evo in Chain[i]) - { - if (!evo.Valid(pkm, lvl, skipChecks)) - continue; - - if (evo.RequiresLevelUp && minLevel >= lvl) - break; // impossible evolution - - oneValid = true; - UpdateMinValues(dl, evo); - int species = evo.Species; - - if (evo.RequiresLevelUp) - lvl--; - dl.Add(evo.GetEvoCriteria(species, lvl)); - break; - } - if (!oneValid) - break; - } - - // Remove future gen pre-evolutions; no Munchlax from a Gen3 Snorlax, no Pichu from a Gen1-only Raichu, etc - var last = dl[dl.Count - 1]; - if (last.Species > maxSpeciesOrigin && dl.Any(d => d.Species <= maxSpeciesOrigin)) - dl.RemoveAt(dl.Count - 1); - - // Last species is the wild/hatched species, the minimum level is 1 because it has not evolved from previous species - last = dl[dl.Count - 1]; - last.MinLevel = 1; - last.RequiresLvlUp = false; - return dl; - } - - private static void UpdateMinValues(IReadOnlyList dl, EvolutionMethod evo) - { - var last = dl[dl.Count - 1]; - if (!evo.RequiresLevelUp) - { - // Evolutions like elemental stones, trade, etc - last.MinLevel = 1; - return; - } - if (evo.Level == 0) - { - // Friendship based Evolutions, Pichu -> Pikachu, Eevee -> Umbreon, etc - last.MinLevel = 2; - - var first = dl[0]; - if (dl.Count > 1 && !first.RequiresLvlUp) - first.MinLevel = 2; // Raichu from Pikachu would have a minimum level of 1; accounting for Pichu (level up required) results in a minimum level of 2 - } - else // level up evolutions - { - last.MinLevel = evo.Level; - - var first = dl[0]; - if (dl.Count > 1) - { - if (first.RequiresLvlUp) - { - if (first.MinLevel <= evo.Level) - first.MinLevel = evo.Level + 1; // Pokemon like Crobat, its minimum level is Golbat minimum level + 1 - } - else - { - if (first.MinLevel < evo.Level) - first.MinLevel = evo.Level; // Pokemon like Nidoqueen who evolve with an evolution stone, minimum level is prior evolution minimum level - } - } - } - last.RequiresLvlUp = evo.RequiresLevelUp; - } - } -} \ No newline at end of file diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionMethod.cs b/PKHeX.Core/Legality/Evolutions/EvolutionMethod.cs index c65912e3c..a1d9382bc 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionMethod.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionMethod.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using static PKHeX.Core.EvolutionType; namespace PKHeX.Core @@ -17,10 +14,7 @@ public sealed class EvolutionMethod public int Level; // Not stored in binary data - public bool RequiresLevelUp; // tracks if this method requires a Level Up - internal IReadOnlyCollection Banlist = Array.Empty(); - - internal static readonly IReadOnlyCollection BanSM = new[] {GameVersion.SN, GameVersion.MN}; + public bool RequiresLevelUp; // tracks if this method requires a Level Up, lazily set /// /// Checks the for validity by comparing against the data. @@ -28,16 +22,10 @@ public sealed class EvolutionMethod /// Entity to check /// Current level /// Option to skip some comparisons to return a 'possible' evolution. - /// + /// True if a evolution criteria is valid. public bool Valid(PKM pkm, int lvl, bool skipChecks) { RequiresLevelUp = false; - - // Check for unavailable evolution methods for an un-traded specimen. - // Example: Sun/Moon lack Ultra's Kantonian evolution methods. - if (!skipChecks && Banlist.Count > 0 && Banlist.Contains((GameVersion)pkm.Version) && pkm.IsUntraded) - return false; - switch ((EvolutionType)Method) { case UseItem: @@ -115,26 +103,14 @@ private bool HasMetLevelIncreased(PKM pkm, int lvl) } } - public EvoCriteria GetEvoCriteria(int species, int lvl) + public EvoCriteria GetEvoCriteria(int species, int form, int lvl) { return new EvoCriteria { Species = species, Level = lvl, - Form = Form, - Method = Method, - }; - } - - public EvolutionMethod Copy(int species, int form) - { - return new EvolutionMethod - { - Method = Method, - Species = species, - Argument = Argument, Form = form, - Level = Level + Method = Method, }; } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs b/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs index 8dd7886cc..4a90f3c5f 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using static PKHeX.Core.GameVersion; namespace PKHeX.Core @@ -38,7 +39,7 @@ static EvolutionTree() Evolves7b = new EvolutionTree(unpack("gg"), Gen7, PersonalTable.GG, Legal.MaxSpeciesID_7b); Evolves8 = new EvolutionTree(unpack("ss"), Gen8, PersonalTable.SWSH, Legal.MaxSpeciesID_8); - // There's always oddballs. + // Throw in banned evolution data! Evolves7.FixEvoTreeSM(); Evolves8.FixEvoTreeSS(); } @@ -74,10 +75,13 @@ internal static EvolutionTree GetEvolutionTree(PKM pkm, int generation) } private readonly IReadOnlyList Entries; - private readonly EvolutionLineage[] Lineage; private readonly GameVersion Game; private readonly PersonalTable Personal; private readonly int MaxSpeciesTree; + private readonly ILookup Lineage; + private static int GetLookupKey(int species, int form) => species | (form << 11); + + #region Constructor private EvolutionTree(IReadOnlyList data, GameVersion game, PersonalTable personal, int maxSpeciesTree) { @@ -85,7 +89,32 @@ private EvolutionTree(IReadOnlyList data, GameVersion game, PersonalTabl Personal = personal; MaxSpeciesTree = maxSpeciesTree; Entries = GetEntries(data); - Lineage = CreateTree(); + var connections = CreateTree(); + Lineage = connections.ToLookup(obj => obj.Key, obj => obj.Value); + } + + private IEnumerable> CreateTree() + { + for (int sSpecies = 1; sSpecies <= MaxSpeciesTree; sSpecies++) + { + var fc = Personal[sSpecies].FormeCount; + for (int sForm = 0; sForm < fc; sForm++) + { + var index = Personal.GetFormeIndex(sSpecies, sForm); + var evos = Entries[index]; + foreach (var evo in evos) + { + var dSpecies = evo.Species; + if (dSpecies == 0) + continue; + var dForm = evo.Form < 0 ? sForm : evo.Form; + var key = GetLookupKey(dSpecies, dForm); + + var link = new EvolutionLink(sSpecies, sForm, evo); + yield return new KeyValuePair(key, link); + } + } + } } private IReadOnlyList GetEntries(IReadOnlyList data) @@ -104,149 +133,34 @@ private IReadOnlyList GetEntries(IReadOnlyList data) }; } - private EvolutionLineage[] CreateTree() - { - var lineage = new EvolutionLineage[Entries.Count]; - for (int i = 0; i < Entries.Count; i++) - lineage[i] = new EvolutionLineage(); - if (Game == Gen6) - Array.Resize(ref lineage, MaxSpeciesTree + 1); - - if (Game.GetGeneration() <= 6) - GenerateEntriesSpeciesOnly(lineage); - else - GenerateEntriesSpeciesForm(lineage); - - return lineage; - } - - private void GenerateEntriesSpeciesOnly(IReadOnlyList lineage) - { - for (int species = 1; species < lineage.Count; species++) - CreateBranch(lineage, species, 0, species); - } - - private void GenerateEntriesSpeciesForm(IReadOnlyList lineage) - { - for (int species = 1; species <= MaxSpeciesTree; species++) - { - var pi = Personal[species]; - var fc = pi.FormeCount; - for (int form = 0; form < fc; form++) - { - var index = Personal.GetFormeIndex(species, form); - CreateBranch(lineage, species, form, index); - } - } - } - - private void CreateBranch(IReadOnlyList lineage, int species, int form, int index) - { - var evos = Entries[index]; - // Iterate over all possible evolutions - foreach (var evo in evos) - CreateLeaf(lineage, evo, species, form, index); - } - - private void CreateLeaf(IReadOnlyList lineage, EvolutionMethod evo, int species, int form, int index) - { - int evolveTo = GetIndex(evo); - if (evolveTo < 0) - return; - - var chainTo = lineage[evolveTo]; - var current = lineage[index]; - var sourceEvo = evo.Copy(species, form); - - chainTo.Insert(sourceEvo); - // If current entries has a pre-evolution, propagate to evolution as well - if (current.Chain.Count != 0) - chainTo.Chain.Insert(0, current.Chain[0]); - - if (evolveTo >= index) - return; - - // If destination species evolves into something (ie a 'baby' Pokemon like Cleffa) - // Add it to the corresponding parent chains - foreach (var method in Entries[evolveTo]) - { - int newIndex = GetIndex(method); - if (newIndex < 0) - continue; - - lineage[newIndex].Insert(sourceEvo); - } - } - private void FixEvoTreeSM() { - UnpackForms((int)Species.Wormadam, 2); - UnpackForms((int)Species.Gastrodon, 1); - UnpackForms((int)Species.Meowstic, 1); - UnpackForms((int)Species.Gourgeist, 3); - - BanEvo((int)Species.Raichu, 1, EvolutionMethod.BanSM); - BanEvo((int)Species.Marowak, 0, EvolutionMethod.BanSM); - BanEvo((int)Species.Raichu, 0, EvolutionMethod.BanSM); + // Sun/Moon lack Ultra's Kantonian evolution methods. + BanEvo((int)Species.Raichu, 1, pkm => pkm.IsUntraded && pkm.SM); + BanEvo((int)Species.Marowak, 0, pkm => pkm.IsUntraded && pkm.SM); + BanEvo((int)Species.Raichu, 0, pkm => pkm.IsUntraded && pkm.SM); } private void FixEvoTreeSS() { - SpreadForms((int)Species.Silvally, 17); + // Gigantamax Pikachu, Meowth-0, and Eevee are prevented from evolving. + BanEvo((int)Species.Raichu, 0, pkm => pkm is IGigantamax g && g.CanGigantamax); + BanEvo((int)Species.Raichu, 1, pkm => pkm is IGigantamax g && g.CanGigantamax); + BanEvo((int)Species.Persian, 0, pkm => pkm is IGigantamax g && g.CanGigantamax); + + foreach (var s in GetEvolutions((int)Species.Eevee, 0)) // Eeveelutions + BanEvo(s, 0, pkm => pkm is IGigantamax g && g.CanGigantamax); } - private void BanEvo(int species, int type, IReadOnlyCollection versionsBanned) + private void BanEvo(int species, int form, Func func) { - var entry = Personal.GetFormeIndex(species, 0); - var lin = Lineage[entry]; - lin.Chain[type][0].Banlist = versionsBanned; + var key = GetLookupKey(species, form); + var node = Lineage[key]; + foreach (var link in node) + link.IsBanned = func; } - private void UnpackForms(int species, int formCount) - { - var baseChain = Lineage[species]; - for (int i = 1; i <= formCount; i++) - { - var entry = Personal.GetFormeIndex(species, i); - var lin = Lineage[entry]; - lin.Chain.Add(new List { baseChain.Chain[0][i] }); - } - baseChain.Chain[0].RemoveRange(1, formCount); - } - - private void SpreadForms(int species, int formCount) - { - var baseChain = Lineage[species]; - for (int i = 1; i <= formCount; i++) - { - var entry = Personal.GetFormeIndex(species, i); - var lin = Lineage[entry]; - lin.Chain.Add(baseChain.Chain[0]); - } - } - - private int GetIndex(PKM pkm) - { - if (pkm.Format < 7) - return pkm.Species; - return Personal.GetFormeIndex(pkm.Species, pkm.AltForm); - } - - private int GetIndex(EvolutionMethod evo) - { - int evolvesToSpecies = evo.Species; - if (evolvesToSpecies == 0) - return -1; - - if (Personal == null) - return evolvesToSpecies; - - int evolvesToForm = evo.Form; - if (evolvesToForm < 0) - evolvesToForm = 0; - - return Personal.GetFormeIndex(evolvesToSpecies, evolvesToForm); - } + #endregion /// /// Gets a list of evolutions for the input by checking each evolution in the chain. @@ -259,10 +173,9 @@ private int GetIndex(EvolutionMethod evo) /// public List GetValidPreEvolutions(PKM pkm, int maxLevel, int maxSpeciesOrigin = -1, bool skipChecks = false, int minLevel = 1) { - int index = GetIndex(pkm); if (maxSpeciesOrigin <= 0) maxSpeciesOrigin = Legal.GetMaxSpeciesOrigin(pkm); - return Lineage[index].GetExplicitLineage(pkm, maxLevel, skipChecks, maxSpeciesOrigin, minLevel); + return GetExplicitLineage(pkm, maxLevel, skipChecks, maxSpeciesOrigin, minLevel); } /// @@ -282,28 +195,161 @@ public IEnumerable GetEvolutionsAndPreEvolutions(int species, int form) private IEnumerable GetPreEvolutions(int species, int form) { - int index = Personal.GetFormeIndex(species, form); + int index = GetLookupKey(species, form); var node = Lineage[index]; - foreach (var methods in node.Chain) + foreach (var method in node) { - foreach (var prevo in methods) - yield return prevo.Species; + var s = method.Species; + var f = method.Form; + yield return s; + var preEvolutions = GetPreEvolutions(s, f); + foreach (var preEvo in preEvolutions) + yield return preEvo; } } private IEnumerable GetEvolutions(int species, int form) { int index = Personal.GetFormeIndex(species, form); - var node = Entries[index]; - foreach (var z in node) + var node = Lineage[index]; + foreach (var method in node) { - var s = z.Species; - if (s == 0) - continue; + var s = method.Species; yield return s; foreach (var next in GetEvolutions(s, form)) yield return next; } } + + /// + /// Generates the reverse evolution path for the input . + /// + /// Entity data + /// Maximum level + /// Skip the secondary checks that validate the evolution + /// Clamp for maximum species ID + /// Minimum level + /// + private List GetExplicitLineage(PKM pkm, int maxLevel, bool skipChecks, int maxSpeciesOrigin, int minLevel) + { + int lvl = maxLevel; + var first = new EvoCriteria { Species = pkm.Species, Level = lvl, Form = pkm.AltForm }; + + const int maxEvolutions = 3; + var dl = new List(maxEvolutions) { first }; + + // There aren't any circular evolution paths, and all lineages have at most 3 evolutions total. + // There aren't any convergent evolution paths, so only yield the first connection. + int species = pkm.Species; + int form = pkm.AltForm; + while (true) + { + var key = GetLookupKey(species, form); + var node = Lineage[key]; + + bool oneValid = false; + foreach (var link in node) + { + if (link.IsEvolutionBanned(pkm)) + continue; + + var evo = link.Method; + if (!evo.Valid(pkm, lvl, skipChecks)) + continue; + + if (evo.RequiresLevelUp && minLevel >= lvl) + break; // impossible evolution + + oneValid = true; + UpdateMinValues(dl, evo); + if (evo.RequiresLevelUp) + lvl--; + + species = link.Species; + form = link.Form; + var detail = evo.GetEvoCriteria(species, form, lvl); + dl.Add(detail); + break; + } + if (!oneValid) + break; + } + + // Remove future gen pre-evolutions; no Munchlax from a Gen3 Snorlax, no Pichu from a Gen1-only Raichu, etc + var last = dl[dl.Count - 1]; + if (last.Species > maxSpeciesOrigin && dl.Any(d => d.Species <= maxSpeciesOrigin)) + dl.RemoveAt(dl.Count - 1); + + // Last species is the wild/hatched species, the minimum level is 1 because it has not evolved from previous species + last = dl[dl.Count - 1]; + last.MinLevel = 1; + last.RequiresLvlUp = false; + return dl; + } + + private static void UpdateMinValues(IReadOnlyList dl, EvolutionMethod evo) + { + var last = dl[dl.Count - 1]; + if (!evo.RequiresLevelUp) + { + // Evolutions like elemental stones, trade, etc + last.MinLevel = 1; + return; + } + if (evo.Level == 0) + { + // Friendship based Evolutions, Pichu -> Pikachu, Eevee -> Umbreon, etc + last.MinLevel = 2; + + var first = dl[0]; + if (dl.Count > 1 && !first.RequiresLvlUp) + first.MinLevel = 2; // Raichu from Pikachu would have a minimum level of 1; accounting for Pichu (level up required) results in a minimum level of 2 + } + else // level up evolutions + { + last.MinLevel = evo.Level; + + var first = dl[0]; + if (dl.Count > 1) + { + if (first.RequiresLvlUp) + { + if (first.MinLevel <= evo.Level) + first.MinLevel = evo.Level + 1; // Pokemon like Crobat, its minimum level is Golbat minimum level + 1 + } + else + { + if (first.MinLevel < evo.Level) + first.MinLevel = evo.Level; // Pokemon like Nidoqueen who evolve with an evolution stone, minimum level is prior evolution minimum level + } + } + } + last.RequiresLvlUp = evo.RequiresLevelUp; + } + + /// + /// Links a to the source and that the method can be triggered from. + /// + private sealed class EvolutionLink + { + public readonly int Species; + public readonly int Form; + public readonly EvolutionMethod Method; + public Func? IsBanned { private get; set; } + + public EvolutionLink(int species, int form, EvolutionMethod method) + { + Species = species; + Form = form; + Method = method; + } + + /// + /// Checks if the is allowed. + /// + /// Entity to check + /// True if banned, false if allowed. + public bool IsEvolutionBanned(PKM pkm) => IsBanned != null && IsBanned(pkm); + } } } diff --git a/PKHeX.Core/Resources/byte/personal_dp b/PKHeX.Core/Resources/byte/personal_dp index 671fa7899990a9814212c844dcac8c1679ab209c..c68bf8a780fe0b35252659c34c99f5b2d873759f 100644 GIT binary patch delta 2960 zcmY+@AlNvvQWS7?7iO#sta!y#_ac|}bKv)I5f?$CTvO$77U!YLlm zZC4Y+B6e_&C-gq0CV^$_;Sz06YxQFat2n?lI-XGz#0=JPgj+NmYQmVqCQfjVu4mOm zv4Cxy;SoL0sfl9=ySPB>RjocuVg>uSLc3E<0Ml5*A#Tw5yqXYZv4LaUq4|QE2FY9g4&7EbYi?we|2Si}y_@r2&@)FiNsJzS#geXV{>VHF*|>sPNl^8b(J za{b???*x4^gLNF?7L5+({zgpAbn42#&oIiAq_ zlbQsUv4=~v#kBe{g;gBj8XZ5Y31SB8IKnL&zo-dg4x2c^J-U8X6U73yafV0q#MQ*H jgk4;q^*60POkxH5xI+7VH33Xx4Ttyjj%_%Heqa3yc5}HR delta 2960 zcmZA0Abt2m}JV5eUo?2m}IwKp+qZ1OkCM0)fDMf8UuTeD;6-zb}F9;labR2M^Ewx^;Ht z+<*I$geZ&NB^NWdasJPZ%Y*$)j{!_!6$iLRr%g==Gg!wFZqax|jfFXE;sp2Tc~ngl z3)sdP9?|!hnmCrQiwm^bwVIg33ife@j>pvmF^x4G;s#w;)r2vN4IJYR-A|~AU>;jI z#RGaBYGPQ#4$kp}{wLKWu#7!iqWvkY0Zd^P2e?M((`rJP!8(p`i-uE;g*j~E1o!B< zrY4F7Y~u`%=zB&@981{61=_A_H8F`5?Bfa@E;T_+V-1J6LD#ct!kEPdj&Xo4yk=Wd`dw!Yvx_X|*tiO`PB!J@2cDVgcJY!z21WP!q=zc5#8WfL0TeSiwH7 z(D9*~Af~Z~L)@V2BQ;^nVgtvxL-)sOBACY(PVs==pqdyKv4e9wq5l`pm|muUY~ zYXDPN#R0C-`I(v!X0VPU+@cXuV_^=PIKe%7K35aP0=996NA%rM6TkDvrCrVow0)s_ z6O&lMKCaLaRujZD)^La$bbYBNj9F~p7!%eMdMpF7Ur;t6WpUGq9%$3Y~w8Q=jDC;JFO+` z;sR~=bZ=r3EBDkpzSn&a(^$hHZqOA~6UHnyaEv>2|DYy|J!8xAL z|C5>oma&IRw8yjtFojhd;2NDjs|jHS>o~$K8o#KqFo#W?;2u4{s)=F&+c?7``r>Nh kSi&wY(Ds{F6O&lMKCaMlUri9xSi|9cy<;2B;on#P0&P>bIRF3v diff --git a/PKHeX.Core/Resources/byte/personal_hgss b/PKHeX.Core/Resources/byte/personal_hgss index cca533668e8bed60c4935b327a038b7089ddd47f..401de3622c6e0fb0db90e244da96f6af20241e1a 100644 GIT binary patch delta 2949 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 Fe*p{+w3Gk< diff --git a/PKHeX.Core/Resources/byte/personal_pt b/PKHeX.Core/Resources/byte/personal_pt index e6a3e24fbd532f554d68ae6c79639846ffa2513d..861dc50bb210435413830b6f0508424cb043198b 100644 GIT binary patch delta 2955 zcmY+@A)(ZdrxUPVPk#gkJ!@kC!#?!*l@+z12$fj}S-a3c_y zFYiVm5C{aqKj6k3fj}S-*o{CS5C{YUa|8l``99xSBz*Sc_k90>0gnvRBg6FPZIkKT zdwNMil!bB0#nkCI|L6MU!D7-|Kc=vXeO#fe_c&vRxIl|rs~=NX#Xhdk_J*1uX0VP! z+@Rx@nlR?DiDTTM>rFLLEMOa_ctDRwO&m+u#W|Ya((1z`R;jI!9BW9)x@xf9h~72z3-?=U>OZupygeyeoSE%ZN3}VuAKb; zWx81Xx9ORnPiC-=L)@U_J*{EPVH3x=L)ZIiqFBH-PVs=A57fl5gk7AY*{{`yNvvQG zmuUS^O#sta!vU_*{*jsxX0d@I+@kYiH4)5X3n#cocR)=Hi`c;#9?|=Wngo{7zy(@9 z)#}F-RfZDx;|GE#R9f*iU;)EQ4_!O>!n@Jb2NXU zdmkpTf<0WKHKZnhX{_M@*J!`1CWKjR;0U+q{8CK>^Vq@(?$P~~niv+bgEKs$H>@TR z)@Ly|FVOO}?){j;D)w=OwtH%Vn87*@af6O;)PymIO&sG6T@f`=EMOa_kv}f)9Cb4o~z4bfY2QZB_9N-%5Q8gjVVgpCGMd$ZwBACY(PH>OzAJoLKh#j2a z5xr+>5?Dq97ifuT^-<;f7rXgi D`DVA| delta 2955 zcmZA1FU;a~0LSrrQ1K2GJ@imHMa2_69z{h(#gkK1JkbZ0J8{DeHv)k`AP@)y+z15b z0~>)rAP@+@fE#lJ0)aqaHv)k`AP@-55eNh(pZ81>Uiya|jy4~?^j#@U~@&#qj0 z&QHWeNf;;UQ-}TXpBtxx`AkoJn8XV9afMcsngFJ;hC|$-{Sh@G%wiMAxI^coY9g4& zHcs(??#I-`u!vopqsgq*iwP`a50_|pT#X-7Sj7RZ(RNi$5Hr}o5pL0OO-&ec*un|! z(PdE+#R7J4hDY=~p(c(cG;o3DC$;)8i52YQ3awA631Av)IK&Owt!hG;#U_q%ht8+f zL@sT3=8Tz%|97|~60?jV1K1^Z-`?x~u>uLg+#u^TB zgZ5i$LYT!Sj&X<1H`GKhk8Pad0o`sjF)U&i=V*FUs}~bk#vU%w@|GGurm%_wT%+x_ znjmJdfg{|a!=omQIc(ts_vkuT6U72{aE3?pysajVB{Xn>=6AIEFo_kkdT(C8dgXtQ z`s%+;uL*c%8f!Sj4cgz;8p13#af~~3zNaRFd2Hhp59oehO$>|J#W|XMTD_RSGWKwZ zmJihUF@;qe;2LcostIBS8#ux(IzCbp#vHbAf_rrN)kLv?9h~72Js+!yV+jphp!pN6 zK1^Z-`?x~ur)mP2#u^TBgZ6-$5N5H7W89(hGc^&+V;iS>~fx?>2uwC zF@a_5;Sw!DHGWKC6$iLR+g&w5%wPjYxJAboYQmVq7EW-Ft}oR@v49<%;SoI{HSv(% zi@|w;=C5?`!z5O)k1Mo(ttNnJtlC<$LNa-|60uDXiiE*Jz8V31S8tIKnMDzE=~*9JX+RdvyJvCW-~@;0%xG zxlj|w5*oNbb5yGjlUTt%uF(3UngFJ;hC|$-{U