From ab6c9a5d2a9fb0dcb10af2f92b77df21ee43ae32 Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Fri, 10 Jun 2016 23:21:48 +0300 Subject: [PATCH] replaced CharEditorSpells activator --- TemplePlus/d20_level.cpp | 32 +++ TemplePlus/d20_level.h | 1 + TemplePlus/ui/ui_char_editor.cpp | 451 +++++++++++++++++-------------- tpdata/tpgamefiles.dat | Bin 117825 -> 117856 bytes 4 files changed, 286 insertions(+), 198 deletions(-) diff --git a/TemplePlus/d20_level.cpp b/TemplePlus/d20_level.cpp index 4a1c253e9..38add4abf 100644 --- a/TemplePlus/d20_level.cpp +++ b/TemplePlus/d20_level.cpp @@ -116,6 +116,38 @@ int D20LevelSystem::GetSurplusXp(objHndl handle){ return gameSystems->GetObj().GetObject(handle)->GetInt32(obj_f_critter_experience) - xpReq; } +int D20LevelSystem::GetSpellsPerLevel(const objHndl handle, Stat classCode, int spellLvl, int casterLvl){ + // todo: extend to non-core + LevelPacket lvlPkt; + auto spellLvlCapped = spellLvl; + if (classCode == stat_level_bard){ + if (spellLvlCapped > 7) + spellLvlCapped = 7; + } + else if (classCode == stat_level_paladin){ + if (spellLvlCapped > 5) + spellLvlCapped = 5; + } + else if (spellLvlCapped > 10){ + spellLvlCapped = 10; + } + + lvlPkt.GetLevelPacket(classCode, handle, 0, casterLvl); + auto spPerLvl = lvlPkt.sorcBardSpellCount[spellLvlCapped]; + + switch (spPerLvl + 40){ + case 0: + case 10: + case 20: + case 30: + spPerLvl = 0; + break; + default: + break; + } + return spPerLvl; +} + void D20LevelSystem::GenerateSpellsPerLevelTables() { diff --git a/TemplePlus/d20_level.h b/TemplePlus/d20_level.h index 33b379333..4b18d6d95 100644 --- a/TemplePlus/d20_level.h +++ b/TemplePlus/d20_level.h @@ -47,6 +47,7 @@ class D20LevelSystem uint32_t GetXpRequireForLevel(uint32_t level); int GetSurplusXp(objHndl handle); + int GetSpellsPerLevel(const objHndl handle, Stat classCode, int spellLvl, int casterLvl); private: void GenerateSpellsPerLevelTables(); }; diff --git a/TemplePlus/ui/ui_char_editor.cpp b/TemplePlus/ui/ui_char_editor.cpp index f9d7a4e2c..4fba7aea8 100644 --- a/TemplePlus/ui/ui_char_editor.cpp +++ b/TemplePlus/ui/ui_char_editor.cpp @@ -123,202 +123,257 @@ BOOL UiCharEditor::SystemInit(GameSystemConf& conf){ } void UiCharEditor::SpellsActivate(){ - //auto handle = GetEditedChar(); - //auto obj = gameSystems->GetObj().GetObject(handle); - //auto &selPkt = GetCharEditorSelPacket(); - - //// get the new caster level for the levelled class (1 indicates a newly taken class) - //auto casterLvlNew = 1; - //auto classLeveled = selPkt.classCode; - //auto lvls = obj->GetInt32Array(obj_f_critter_level_idx); - //for (auto i = 0u; i < lvls.GetSize(); i++){ - // auto classCode = static_cast(lvls[i]); - // if (classLeveled == classCode) // is the same the one being levelled - // casterLvlNew++; - //} - - - //auto &needPopulateEntries = temple::GetRef(0x10C4D4C4); - - //static auto setScrollbars = []() { - // auto sbId = temple::GetRef(0x10C737C0); - // ui.ScrollbarSetY(sbId, 0); - // auto numEntries = temple::GetRef(0x10C75A60); - // ui.ScrollbarSetYmax(sbId, max(0, numEntries - 20)); - // temple::GetRef(0x10C73C34) = 0; // scrollbarY - // ui.WidgetCopy(sbId, &temple::GetRef(0x10C736C0)); - - // auto &charEdSelPkt = uiCharEditor.GetCharEditorSelPacket(); - // auto sbAddedId = temple::GetRef(0x10C4D548); - // ui.ScrollbarSetY(sbAddedId, 0); ui.ScrollbarSetYmax(sbAddedId, max(0, charEdSelPkt.spellEnumsAddedCount - 20)); - // temple::GetRef(0x10C75BC0) = 0; // scrollbarY - // ui.WidgetCopy(sbAddedId, &temple::GetRef(0x10C75AC0)); - //}; - - //if (!needPopulateEntries) { - // setScrollbars(); - // return; - //} - - - //auto & spellFlags = temple::GetRef(0x10C72F20); - //memset(spellFlags, 0, sizeof(spellFlags)); - //selPkt.spellEnumToRemove = 0; - - - //auto isNewClass = casterLvlNew == 1; // todo: generalize for PrC's - - //// newly taken class - //if (isNewClass){ - // - // // let natural casters choose 4 cantrips - // auto count = 0; - // // todo: generalize to handle new base classes - // if (classLeveled == stat_level_bard || classLeveled == stat_level_sorcerer ){ - // selPkt.spellEnums[count++] = 803; // Spell Level 0 label - // for (int i = 1; i < 5; i++) { - // selPkt.spellEnums[count] = 802; - // spellFlags[count++] = 3; - // } - // - // selPkt.spellEnumsAddedCount = count; - // } - // - // // add 2 level 1 spells for wiz/sorc - // // todo: generalize to handle new base classes - // if (classLeveled == stat_level_wizard || classLeveled == stat_level_sorcerer){ - // selPkt.spellEnums[count++] = 804; // Spell Level 1 label - // for (int i = 0; i < 2; i++) { - // selPkt.spellEnums[count] = 802; - // spellFlags[count++] = 3; - // } - // selPkt.spellEnumsAddedCount = count; - // } - - // // bonus spells for wizards - // if (classLeveled == stat_level_wizard){ - // auto intScore = objects.StatLevelGet(handle, stat_intelligence); - // if (selPkt.statBeingRaised == stat_intelligence) - // intScore++; - // auto intScoreMod = objects.GetModFromStatLevel(intScore); - // for (auto i = 0; i < intScoreMod; i++){ - // selPkt.spellEnums[count] = 802; - // spellFlags[count++] = 3; - // } - // selPkt.spellEnumsAddedCount = count; - // } - //} - // - //// progressing with a class todo: generalize for PrC's - //else{ - // // 2 new spells for Vancians - // if (d20ClassSys.IsVancianCastingClass(classLeveled, handle) ){ - // for (int i = 0; i < 2; i++) { - // selPkt.spellEnums[selPkt.spellEnumsAddedCount] = SPELL_ENUM_VACANT; - // spellFlags[selPkt.spellEnumsAddedCount++] = 3; - // } - // } - - // // innate casters - show all known spells and add slots as necessary - // if (d20ClassSys.IsNaturalCastingClass(classLeveled, handle)){ - - // std::vector knownSpells; - // knownSpells.reserve(SPELL_ENUM_MAX_EXPANDED); - // // get all known spells for innate casters - // int numKnown = critterSys.GetSpellEnumsKnownByClass(handle, spellSys.GetSpellClass(classLeveled), &knownSpells[0], SPELL_ENUM_MAX_EXPANDED); - // selPkt.spellEnumsAddedCount = numKnown; - - // - // // get max spell level todo: extend! - // auto maxSpellLvl = casterLvlNew / 2; - // if (classLeveled == stat_level_bard) { - // maxSpellLvl = (casterLvlNew - 1) / 3 + 1; - // } - - // // add labels - // for (int spellLvl = 0u; spellLvl <= maxSpellLvl; spellLvl++) { - // //selPkt.spellEnums[selPkt.spellEnumsAddedCount++] = SPELL_ENUM_LABEL_START + spellLvl; - // knownSpells.push_back(SPELL_ENUM_LABEL_START + spellLvl); - // } - - // for (auto spellLvl = 0; spellLvl < maxSpellLvl; spellLvl++) { - // int numNewSpellsForLevel = - // d20LevelSys.GetSpellsPerLevel(handle, selPkt.classCode, spellLvl, casterLvlNew) - // - d20LevelSys.GetSpellsPerLevel(handle, selPkt.classCode, spellLvl, casterLvlNew-1); - // for (int i = 0; i < numNewSpellsForLevel; i++){ - // //selPkt.spellEnums[selPkt.spellEnumsAddedCount++] = SPELL_ENUM_NEW_SLOT_START + spellLvl ; - // knownSpells.push_back(SPELL_ENUM_NEW_SLOT_START + spellLvl); - // } - // } - // auto spellClass = spellSys.GetSpellClass(selPkt.classCode); - // std::sort(knownSpells.begin(), knownSpells.end(), [spellClass,handle](int first, int second){ - // auto firstIsLabel = (first >= SPELL_ENUM_LABEL_START) && (first < SPELL_ENUM_LABEL_START + 10); - // auto secondIsLabel = (second >= SPELL_ENUM_LABEL_START) && (second < SPELL_ENUM_LABEL_START + 10); - - // auto firstIsNewSlot = (first >= SPELL_ENUM_NEW_SLOT_START) && (first < SPELL_ENUM_NEW_SLOT_START + 10); - // auto secondIsNewSlot = (second >= SPELL_ENUM_NEW_SLOT_START) && (second < SPELL_ENUM_NEW_SLOT_START + 10); - - // auto firstSpellLvl = 0; - // if (firstIsLabel) - // firstSpellLvl = first - SPELL_ENUM_LABEL_START; - // else if (firstIsNewSlot) - // firstSpellLvl = first - SPELL_ENUM_NEW_SLOT_START; - // else - // firstSpellLvl = spellSys.GetSpellLevelBySpellClass(first, spellClass ,handle); - - // auto secondSpellLvl = 0; - // if (secondIsLabel) - // secondSpellLvl = first - SPELL_ENUM_LABEL_START; - // else if (secondIsNewSlot) - // secondSpellLvl = first - SPELL_ENUM_NEW_SLOT_START; - // else - // secondSpellLvl = spellSys.GetSpellLevelBySpellClass(second, spellClass ,handle); - - // if (firstSpellLvl != secondSpellLvl) - // return firstSpellLvl - secondSpellLvl; - - // if (firstIsLabel) - // return -1; - // if (secondIsLabel) - // return 1; - - // if (firstIsNewSlot){ - // if (secondIsNewSlot) - // return 0; - // else - // return 1; - // } - - // if (secondIsNewSlot) - // return -1; - - // return _stricmp(spellSys.GetSpellMesline(second), spellSys.GetSpellMesline(first) ); - - // }); - - // // convert the "New Spell Slot" enums to vacant enums (they were just used for sorting) - // for (auto i = 0u; i < knownSpells.size(); i++) { - // if (knownSpells[i] >= SPELL_ENUM_NEW_SLOT_START && knownSpells[i] < SPELL_ENUM_NEW_SLOT_START + 10){ - // knownSpells[i] = 802; - // spellFlags[i] = 3; - // } - // } - - // // mark old replaceable spells every 2rd level for sorcs / 3rd level for bards - - // if (numKnown > 0){ - // auto numSlots = selPkt.spellEnumsAddedCount; - - // } - // } - - - //} - - //// populate entries - - //setScrollbars(); - //needPopulateEntries = 0; // needPopulateEntries + auto handle = GetEditedChar(); + auto obj = gameSystems->GetObj().GetObject(handle); + auto &selPkt = GetCharEditorSelPacket(); + + + + // get the new caster level for the levelled class (1 indicates a newly taken class) + auto casterLvlNew = 1; + auto classLeveled = selPkt.classCode; + auto lvls = obj->GetInt32Array(obj_f_critter_level_idx); + for (auto i = 0u; i < lvls.GetSize(); i++){ + auto classCode = static_cast(lvls[i]); + if (classLeveled == classCode) // is the same the one being levelled + casterLvlNew++; + } + + auto &needPopulateEntries = temple::GetRef(0x10C4D4C4); + + static auto setScrollbars = []() { + auto sbId = temple::GetRef(0x10C737C0); + ui.ScrollbarSetY(sbId, 0); + auto numEntries = temple::GetRef(0x10C75A60); + ui.ScrollbarSetYmax(sbId, max(0, numEntries - 20)); + temple::GetRef(0x10C73C34) = 0; // scrollbarY + ui.WidgetCopy(sbId, &temple::GetRef(0x10C736C0)); + + auto &charEdSelPkt = uiCharEditor.GetCharEditorSelPacket(); + auto sbAddedId = temple::GetRef(0x10C4D548); + ui.ScrollbarSetY(sbAddedId, 0); ui.ScrollbarSetYmax(sbAddedId, max(0, charEdSelPkt.spellEnumsAddedCount - 20)); + temple::GetRef(0x10C75BC0) = 0; // scrollbarY + ui.WidgetCopy(sbAddedId, &temple::GetRef(0x10C75AC0)); + }; + + if (!needPopulateEntries) { + setScrollbars(); + return; + } + + + auto & spellFlags = temple::GetRef(0x10C72F20); + memset(spellFlags, 0, sizeof(spellFlags)); + selPkt.spellEnumToRemove = 0; + + + auto isNewClass = casterLvlNew == 1; // todo: generalize for PrC's + + // newly taken class + if (isNewClass){ + + // let natural casters choose 4 cantrips + auto count = 0; + // todo: generalize to handle new base classes + if (classLeveled == stat_level_bard || classLeveled == stat_level_sorcerer ){ + selPkt.spellEnums[count++] = 803; // Spell Level 0 label + for (int i = 1; i < 5; i++) { + selPkt.spellEnums[count] = 802; + spellFlags[count++] = 3; + } + + selPkt.spellEnumsAddedCount = count; + } + + // add 2 level 1 spells for wiz/sorc + // todo: generalize to handle new base classes + if (classLeveled == stat_level_wizard || classLeveled == stat_level_sorcerer){ + selPkt.spellEnums[count++] = 804; // Spell Level 1 label + for (int i = 0; i < 2; i++) { + selPkt.spellEnums[count] = 802; + spellFlags[count++] = 3; + } + selPkt.spellEnumsAddedCount = count; + } + + // bonus spells for wizards + if (classLeveled == stat_level_wizard){ + auto intScore = objects.StatLevelGet(handle, stat_intelligence); + if (selPkt.statBeingRaised == stat_intelligence) + intScore++; + auto intScoreMod = objects.GetModFromStatLevel(intScore); + for (auto i = 0; i < intScoreMod; i++){ + selPkt.spellEnums[count] = 802; + spellFlags[count++] = 3; + } + selPkt.spellEnumsAddedCount = count; + } + } + + // progressing with a class todo: generalize for PrC's + else{ + // 2 new spells for Vancians + if (d20ClassSys.IsVancianCastingClass(classLeveled, handle) ){ + for (int i = 0; i < 2; i++) { + selPkt.spellEnums[selPkt.spellEnumsAddedCount] = SPELL_ENUM_VACANT; + spellFlags[selPkt.spellEnumsAddedCount++] = 3; + } + } + + // innate casters - show all known spells and add slots as necessary + if (d20ClassSys.IsNaturalCastingClass(classLeveled, handle)){ + + struct KnownSpellInfo{ + int spEnum = 0; + char spFlag = 0; + KnownSpellInfo(int a, int b) :spEnum(a), spFlag(b){}; + }; + std::vector knownSpells; + knownSpells.reserve(SPELL_ENUM_MAX_EXPANDED); + // get all known spells for innate casters + int _knownSpells[3999] = {0,}; + int numKnown = critterSys.GetSpellEnumsKnownByClass(handle, spellSys.GetSpellClass(classLeveled), &_knownSpells[0], SPELL_ENUM_MAX_EXPANDED); + for (int i = 0; i < numKnown; i++){ + knownSpells.push_back(KnownSpellInfo( _knownSpells[i],0 )); + } + selPkt.spellEnumsAddedCount = numKnown; + + + // get max spell level todo: extend! + auto maxSpellLvl = casterLvlNew / 2; + if (classLeveled == stat_level_bard) { + maxSpellLvl = (casterLvlNew - 1) / 3 + 1; + } + + // add labels + for (int spellLvl = 0u; spellLvl <= maxSpellLvl; spellLvl++) { + //selPkt.spellEnums[selPkt.spellEnumsAddedCount++] = SPELL_ENUM_LABEL_START + spellLvl; + knownSpells.push_back(KnownSpellInfo(SPELL_ENUM_LABEL_START + spellLvl, 0)); + } + + for (auto spellLvl = 0; spellLvl <= maxSpellLvl; spellLvl++) { + int numNewSpellsForLevel = + d20LevelSys.GetSpellsPerLevel(handle, classLeveled, spellLvl, casterLvlNew) + - d20LevelSys.GetSpellsPerLevel(handle, classLeveled, spellLvl, casterLvlNew-1); + for (int i = 0; i < numNewSpellsForLevel; i++){ + //selPkt.spellEnums[selPkt.spellEnumsAddedCount++] = SPELL_ENUM_NEW_SLOT_START + spellLvl ; + knownSpells.push_back(KnownSpellInfo(SPELL_ENUM_NEW_SLOT_START + spellLvl, 0)); + } + } + auto spellClass = spellSys.GetSpellClass(classLeveled); + std::sort(knownSpells.begin(), knownSpells.end(), [spellClass,handle](KnownSpellInfo &ksiFirst, KnownSpellInfo &ksiSecond){ + auto first = ksiFirst.spEnum, second = ksiSecond.spEnum; + + auto firstIsLabel = (first >= SPELL_ENUM_LABEL_START) && (first < SPELL_ENUM_LABEL_START + 10); + auto secondIsLabel = (second >= SPELL_ENUM_LABEL_START) && (second < SPELL_ENUM_LABEL_START + 10); + + auto firstIsNewSlot = (first >= SPELL_ENUM_NEW_SLOT_START) && (first < SPELL_ENUM_NEW_SLOT_START + 10); + auto secondIsNewSlot = (second >= SPELL_ENUM_NEW_SLOT_START) && (second < SPELL_ENUM_NEW_SLOT_START + 10); + + auto firstSpellLvl = 0; + if (firstIsLabel) + firstSpellLvl = first - SPELL_ENUM_LABEL_START; + else if (firstIsNewSlot) + { + firstSpellLvl = first - SPELL_ENUM_NEW_SLOT_START; + } + else + firstSpellLvl = spellSys.GetSpellLevelBySpellClass(first, spellClass ,handle); + + auto secondSpellLvl = 0; + if (secondIsLabel) + secondSpellLvl = second - SPELL_ENUM_LABEL_START; + else if (secondIsNewSlot) + { + secondSpellLvl = second - SPELL_ENUM_NEW_SLOT_START; + } + else + secondSpellLvl = spellSys.GetSpellLevelBySpellClass(second, spellClass ,handle); + + if (firstSpellLvl != secondSpellLvl) + return firstSpellLvl < secondSpellLvl; + + // if they are the same level + + if (firstIsLabel){ + return !secondIsLabel; + } + if (secondIsLabel) + return false; + + if (firstIsNewSlot){ + return false; + } + + if (secondIsNewSlot){ + return true; + } + + + auto name1 = spellSys.GetSpellName(first); + auto name2 = spellSys.GetSpellName(second); + auto nameCmp = _strcmpi(name1, name2); + return nameCmp < 0; + + //return _stricmp(spellSys.GetSpellMesline(second), spellSys.GetSpellMesline(first) ); + + }); + + // convert the "New Spell Slot" enums to vacant enums (they were just used for sorting) + for (auto i = 0u; i < knownSpells.size(); i++) { + if (knownSpells[i].spEnum >= SPELL_ENUM_NEW_SLOT_START && knownSpells[i].spEnum < SPELL_ENUM_NEW_SLOT_START + 10){ + knownSpells[i].spEnum = 802; + knownSpells[i].spFlag = 3; + } + } + + // mark old replaceable spells for sorc lvls 4,6,8,... and bards 5,8,11,... + + bool isReplacingSpells = false; + if (classLeveled == stat_level_bard){ + if (casterLvlNew >= 5 + && !((casterLvlNew -5) % 3) ) + isReplacingSpells = true; + } + if (classLeveled == stat_level_sorcerer){ + if (casterLvlNew >= 4 && !(casterLvlNew % 2)){ + isReplacingSpells = true; + } + } + if (isReplacingSpells){ + auto spLvlReplaceable = maxSpellLvl - 2; + for (auto i = 0u; i < knownSpells.size(); i++){ + auto spEnum = knownSpells[i].spEnum; + auto spLvl = 0; + if (spEnum >= SPELL_ENUM_LABEL_START && spEnum < SPELL_ENUM_LABEL_START + 10) + spLvl = spEnum - SPELL_ENUM_LABEL_START; + else if (spEnum != SPELL_ENUM_MAX) + { + spLvl = spellSys.GetSpellLevelBySpellClass(spEnum, spellClass, handle); + if (spLvl > spLvlReplaceable) + break; + knownSpells[i].spFlag = 1; // denotes as replaceable + } + } + } + + selPkt.spellEnumsAddedCount = min(802u, knownSpells.size()); + for (auto i = 0u; i < knownSpells.size(); i++) { + selPkt.spellEnums[i] = knownSpells[i].spEnum; + spellFlags[i] = knownSpells[i].spFlag; + } + + } + + + } + + // populate entries + temple::GetRef(0x101A7390)(); // CharEditorLearnableSpellEntriesListPopulate + temple::GetRef(0x101A5F30)(); // CharEditorSpellCountBoxesUpdate + + setScrollbars(); + needPopulateEntries = 0; // needPopulateEntries } int &UiCharEditor::GetState(){ @@ -339,9 +394,9 @@ class UiCharEditorHooks : public TempleFix { void apply() override { - /*replaceFunction(0x101A75F0, [](){ + replaceFunction(0x101A75F0, [](){ uiCharEditor.SpellsActivate(); - });*/ + }); } } uiCharEditorHooks; diff --git a/tpdata/tpgamefiles.dat b/tpdata/tpgamefiles.dat index 58a84adbf36f27f1ff84d96be7c6f607d9b9de1e..5db9849fe1ed2e837a97a64099b85cca5daf22ea 100644 GIT binary patch delta 1523 zcmYk44OEO-7{~9^XgeBdq9km#cG9OB?a1^&#ULEYQ0owxLn_B4N;r#_e1y){EV|ic zPtCSzRm{>PZPQFMHBF5utdfwS$mwI%(jsf0X6EdA&$;ja-v9kQ&-32ry*0TOow*j$ zr|Z*hC8l%6X$HiDA)I;x%i?YDiw^m>5%kbi4ySKR&WcK&OwechGt*d_geBX{< zc5&|dZRMvUL-H3{t`$j}szhe*+j?hqwN$!sm^n*JQ`_?Iy7o5R8Da<+l@;ASx$(H6#E%vw-vp|}xnv!z!oKz+vjRo(N zYvp_Ynby8DtuiU1yvJeRapAU+>4y${r>U1+IsMD9D ztk6M~RN6gldtY?4^sCgCtVI3aG85w^73it^=9g?Fz4X!#; zG*@Eh)!A4g?u&SFXLr8c%J{?^b98&ERt&YTomhN$aEco@EcC?Z?>c(?*4P)P7dp4+ zxO9iEa;b9+eD?Nk!0W$)bk-l{HrPKX%i*!32GVwW#F`(-jIe*WvE-g5ujPB&xV#fJ zz}zn?2+RMmaBG}GvvE_dEIu>f=hk_oD`xD2G`n5pv+7 z9FYTuk&6Kpa`0m`$rJv~iPLokA6 zYM75QY~1s$H6Wr|2txB>Erg=<)?zL_ol%FRo+i|xsi(zti0J8yItW2=*BDk?G=|j$ z4a`HcTZ0uat{#i)>4thkua4J42vbkY@Q*m*%bwn?hd}F33?SqUb=1O_EWUyIfl0^{ z1|e1(=}Ij)SsUGW#NK0$etJ*~_KXT2Q-h^im}H~%G3GlumJl_D_Rv->_}FBdY?%uo zXRHV*q4o{n6l-L?F<}`d3{53u4(dmI0#Pk+Z1&}NC+#IkEF&6@s1WVfpP)e|G$AEr z6wNT90kK%dfsiPAz5#qp<`AB{5p$S{X-5ONn+~7NC8Q6}dV|_Fg15<3;LWv0{5oCP z2%aXBX-;)`a{E<;78W delta 1472 zcmZ{idr;I>6vxl;r>^n{5ClYsTp|S;Sad}mDJZ#`CMJT3k6A6YVQ@lT5jFvp@)(s3 zLc9!G0zm?rq7!Pk%kHx7E-V5z1TmPVuDU}rx*DTn!B0Ex?qq+|Z|3gz-tYaKbM8HR zZ`24hHG($N_99KjUXfFYrSgxL_F&^CW!8yJdAnk&(_@4o-m7Q(9%}Sc@J>gXjTi1b zd-~fM)9sF|{_lbVu7S=oXib*7PC3>v-V*H-Tvd=O9Y zUX9(J(j1>PP5S;($wX9^BPEH0Ege-hLt^%3mLE$$N#S-n`^cmal-pUHDGVSXI?6?ZvAjBTvVSEBy9{Jn=D!M`Ne*v)1)BiIO}|OXTOan9L(} zY|2DaR@{|cleZV#EYL+BZci}f9KBfnfu?`5Beid2ur6=zBe}O%f@F*A#yycVEiK~w z>-ER(NE}=gW^;awWXJxIP*Eid z6RJ@}vQbUxnhBAsaXxqBA6AWXXxFU9#oPlj{WVnn6%U?m)u5Q$Op~uP7)eC02A7a< zT^L2Ow2LldVY#-|sV*yG?y@5OT8ySMZPAj+vnnmd5;>^F6(pXQta}@5a_|zqL1xb- zTu$PsqtrZGsUwnSWjZo>X4DbFGl3rCNLK5ut76+qug7I%PBTyiiZf7ko)s9Z-D);q zoFflY=s)4;X9gQLV2sNPLx2EgwzL}qTrF0r))C+)2XJ4?GP`k}i_J|VY=UwOu)1#a z;?!aG77uizr`weOL=Vo3wy~j<@CQmL%mY|L z`i#C9QUQ(Bg`&{RJ&@^YL)R0!lk8zu*@M3Jvq#GSPR^lNrs%;iJ2_4_G!kv9U=uwU zWJfE+0Qcxt-?K$V47HmodZjLe*Ro6_hS*KVX#gQ)wy;(s`q<63;{Y-tz|U;(1sS^r zpu+(miaGb^`aIRp?- zVb8np{F_emoZTN~q(T|JJnLhgaT#BZw!rfOpWU&IzlH%;{VQtBJ2Wyv=fCQp-#6jM RMCGaBNz31qQc0}~z~3-TM`!>5