From 828d8074647291ae42926d91cb3db644df074a73 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Wed, 21 Aug 2024 09:34:42 +0100 Subject: [PATCH] Canonise passive carp (#85939) ## About The Pull Request The other day someone posted a story on the tgstation forums about being spawned as a sentient "passive carp" from slime gold cores and then biting someone to death which mildly annoyed me because "passive carp" is a mob which exists purely for a shuttle event, to make them less dangerous when passing through the area. And _also_ because once sapient the passive carp was, of course, not passive at all. If it was a passive carp they wouldn't have had to appeal a note, because they wouldn't have been able to bite that guy. There were two solutions for this. The obvious one is "remove this mob from the gold slime core pool". The one I chose instead was "give this mob more divergent behaviour". Now instead of "passive carp" it is called a "false carp", which is a carp-like creature that is incapable of harming humans (it has the pacifist trait and does no damage). It is spawned from the _friendly_ gold core reaction instead of the hostile one. It will be chill until someone attacks it or it sees someone attack another false carp, then all of them will run away from that person. Additionally I fixed a bug where its teeth reappeared in some animation states. And another bug where crab AI just didn't work. ## Why It's Good For The Game If you see a mob called "passive carp" it should do what it says in the name. ## Changelog :cl: balance: Pacifist carp can now be spawned from the friendly gold core reaction, and no longer appear from the hostile one. fix: The teeth of toothless carp will not occasionally reappear fix: Crabs will now run from attackers larger than them and attack things smaller than them, as intended. /:cl: --- code/__DEFINES/ai/ai_blackboard.dm | 3 +++ .../simple_find_nearest_target_to_flee.dm | 1 + .../basic_subtrees/simple_find_target.dm | 7 +++++- .../basic_targeting_strategy.dm | 15 ++++++++++++ .../mob/living/basic/space_fauna/carp/carp.dm | 23 +++++++++++++----- .../space_fauna/carp/carp_ai_rift_actions.dm | 14 ++++++++--- .../space_fauna/carp/carp_controllers.dm | 17 ++++++------- code/modules/mob/living/basic/vermin/crab.dm | 1 - icons/mob/simple/carp.dmi | Bin 36046 -> 36024 bytes 9 files changed, 60 insertions(+), 21 deletions(-) diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index baf166d2a7a..0c682f7d411 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -133,6 +133,9 @@ ///Range for a MOD AI controller. #define MOD_AI_RANGE 200 +///Only target mobs with these traits +#define BB_TARGET_ONLY_WITH_TRAITS "BB_target_only_with_traits" + ///should we skip the faction check for the targeting strategy? #define BB_ALWAYS_IGNORE_FACTION "BB_always_ignore_factions" ///are we in some kind of temporary state of ignoring factions when targeting? can result in volatile results if multiple behaviours touch this diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm index d9e0d1e7fb9..83e514f3270 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm @@ -21,4 +21,5 @@ controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list/nearest, BB_BASIC_MOB_RETALIATE_LIST, target_key, targeting_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key + target_key = BB_BASIC_MOB_FLEE_TARGET targeting_key = BB_FLEE_TARGETING_STRATEGY diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm index 42608730f98..759355283ac 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm @@ -1,8 +1,10 @@ /datum/ai_planning_subtree/simple_find_target + /// Variable to store target in + var/target_key = BB_BASIC_MOB_CURRENT_TARGET /datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - controller.queue_behavior(/datum/ai_behavior/find_potential_targets, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + controller.queue_behavior(/datum/ai_behavior/find_potential_targets, target_key, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) // Prevents finding a target if a human is nearby /datum/ai_planning_subtree/simple_find_target/not_while_observed @@ -12,3 +14,6 @@ if(watcher.stat != DEAD) return return ..() + +/datum/ai_planning_subtree/simple_find_target/to_flee + target_key = BB_BASIC_MOB_FLEE_TARGET diff --git a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm index 320e6abf0be..709acb8d5e8 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm @@ -88,6 +88,21 @@ // trust fall exercise return TRUE +/datum/targeting_strategy/basic/require_traits + +/datum/targeting_strategy/basic/require_traits/can_attack(mob/living/living_mob, atom/the_target, vision_range) + . = ..() + if (!.) + return FALSE + var/list/required_traits = living_mob.ai_controller.blackboard[BB_TARGET_ONLY_WITH_TRAITS] + if (!length(required_traits)) + return TRUE + + for (var/trait as anything in required_traits) + if (HAS_TRAIT(the_target, trait)) + return TRUE + return FALSE + /// Subtype which searches for mobs of a size relative to ours /datum/targeting_strategy/basic/of_size /// If true, we will return mobs which are smaller than us. If false, larger. diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index bf1abd371aa..cf56a01a684 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -45,6 +45,8 @@ maximum_survivable_temperature = 1500 death_offset_y = -10 + /// If true we will run away from attackers even at full health + var/cowardly = FALSE /// Cytology cells you can swab from this creature var/cell_line = CELL_LINE_TABLE_CARP /// What colour is our 'healing' outline? @@ -89,7 +91,8 @@ if (cell_line) AddElement(/datum/element/swabable, cell_line, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) AddElement(/datum/element/simple_flying) - AddElement(/datum/element/ai_flee_while_injured) + if (!cowardly) + AddElement(/datum/element/ai_flee_while_injured) setup_eating() AddComponent(/datum/component/aggro_emote, emote_list = string_list(list("gnashes"))) @@ -268,8 +271,8 @@ ///Wild carp that just vibe ya know /mob/living/basic/carp/passive - name = "passive carp" - desc = "A timid, sucker-bearing creature that resembles a fish. " + name = "false carp" + desc = "A close relative of the space carp which is entirely toothless and feeds by stealing its cousin's leftovers." icon_state = "base_friend" icon_living = "base_friend" @@ -279,11 +282,19 @@ attack_verb_continuous = "suckers" attack_verb_simple = "suck" - melee_damage_lower = 4 - melee_damage_upper = 4 + melee_damage_lower = 0 + melee_damage_upper = 0 + cowardly = TRUE ai_controller = /datum/ai_controller/basic_controller/carp/passive + gold_core_spawnable = FRIENDLY_SPAWN /mob/living/basic/carp/passive/Initialize(mapload) . = ..() - AddElement(/datum/element/ai_retaliate) + AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(on_attacked))) AddElement(/datum/element/pet_bonus, "bloops happily!") + ADD_TRAIT(src, TRAIT_PACIFISM, INNATE_TRAIT) + +/// If someone slaps one of the school, scatter +/mob/living/basic/carp/passive/proc/on_attacked(mob/living/attacker) + for(var/mob/living/basic/carp/passive/schoolmate in oview(src, 9)) + schoolmate.ai_controller?.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, attacker) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm index f104d3566b3..260dce64bdc 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm @@ -7,18 +7,20 @@ var/datum/ai_behavior/rift_behaviour /// If true we finish planning after this var/finish_planning = FALSE + /// Key to read for flee target + var/target_key = BB_BASIC_MOB_CURRENT_TARGET /datum/ai_planning_subtree/make_carp_rift/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) if (!rift_behaviour) CRASH("Forgot to specify rift behaviour for [src]") - if (!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + if (!controller.blackboard_key_exists(target_key)) return var/datum/action/cooldown/using_action = controller.blackboard[BB_CARP_RIFT] if (!using_action?.IsAvailable()) return - controller.queue_behavior(rift_behaviour, BB_CARP_RIFT, BB_BASIC_MOB_CURRENT_TARGET) + controller.queue_behavior(rift_behaviour, BB_CARP_RIFT, target_key) if (finish_planning) return SUBTREE_RETURN_FINISH_PLANNING @@ -31,13 +33,17 @@ finish_planning = TRUE /datum/ai_planning_subtree/make_carp_rift/panic_teleport/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/fleeing_from = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/atom/movable/fleeing_from = controller.blackboard[target_key] if(!QDELETED(fleeing_from) && controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(fleeing_from, TRAIT_SCARY_FISHERMAN)) return ..() if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) return return ..() +/datum/ai_planning_subtree/make_carp_rift/panic_teleport/flee_key + target_key = BB_BASIC_MOB_FLEE_TARGET + + /** * # Make carp rift (aggressive) * Plan to teleport towards our target so we can fuck them up @@ -46,7 +52,7 @@ rift_behaviour = /datum/ai_behavior/make_carp_rift/towards/aggressive /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/atom/movable/target = controller.blackboard[target_key] if(!QDELETED(target) && controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) return return ..() diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm index 4491ef11491..ae011f5b14a 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm @@ -106,22 +106,21 @@ */ /datum/ai_controller/basic_controller/carp/passive blackboard = list( - BB_BASIC_MOB_STOP_FLEEING = TRUE, - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/require_traits, + BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, - BB_TARGET_PRIORITY_TRAIT = TRAIT_SCARY_FISHERMAN, BB_CARPS_FEAR_FISHERMAN = TRUE, + BB_TARGET_ONLY_WITH_TRAITS = list(TRAIT_SCARY_FISHERMAN), ) ai_traits = STOP_MOVING_WHEN_PULLED planning_subtrees = list( /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target/from_fisherman, + /datum/ai_planning_subtree/simple_find_target/to_flee, // This should only find master fishermen because of the targeting strategy + /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key, + /datum/ai_planning_subtree/make_carp_rift/panic_teleport/flee_key, + /datum/ai_planning_subtree/flee_target/from_flee_key, /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman, + /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/carp_migration, ) diff --git a/code/modules/mob/living/basic/vermin/crab.dm b/code/modules/mob/living/basic/vermin/crab.dm index 0183be667b8..3c1c9146a06 100644 --- a/code/modules/mob/living/basic/vermin/crab.dm +++ b/code/modules/mob/living/basic/vermin/crab.dm @@ -89,7 +89,6 @@ planning_subtrees = list( /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key, /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/target_retaliate/to_flee, /datum/ai_planning_subtree/simple_find_target, /datum/ai_planning_subtree/basic_melee_attack_subtree, /datum/ai_planning_subtree/random_speech/crab, diff --git a/icons/mob/simple/carp.dmi b/icons/mob/simple/carp.dmi index 1be59c43ecb6e31cab8235e38369c53e66834fd9..e33b9c7ada81035cae96c05135ff0f2b3f3b717f 100644 GIT binary patch delta 6064 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

W<3Ne{w0Yj^ff)^r= z4Vf$OAFX-@tiUslpKk)|VG&q?CV7@chH7wr3phHI0UxR0&(()=)8B4mdb zPAK@E864jJ(JAt(=5_$EL_u@sM)=t9*lly*Wt72#QKq2;?PAWrp}$11mlYCpwrVCM zr2jK8c}k;-`4U_gRcup^26QhV%RUoF6dNt%!`q8fg8p*^DVe@6$-Dk z1MOv12I|hpuljQ;e~H*V_U~Do#oKn7Ez13T>)Wj}SLtK7;bSKSP_hd(0d;Lc z=S@=ppgg^R_elO4VTdXTw)tv(kK~44$n&G;-MN6xf+_Rif>|tVA3%wcg0C`yqlyCJ zS66ZPed0|ic*g*lW*8axi5T?s;r_ip*Zn@sp(|uJE>m^@_#K@>P<*t`|I8J;;d@O_ z!wJ@{c-pTui4>z*yNGc=axM}GaxQo*cO z(|n#E*8S#f-DOWk7XaWcz|9Lz9`1VHM_s@}w;rmVGusbTjS?FiU#EdRi$05aci=+m>$xnh2~^y00s{{x%xYm5K@ delta 6118 zcmXw72|QHm`#)pJ#Vv)XEYoEWLJKK7iKgr+vPUVz*q5;!rR7FaGZ!ISg^(@#GKNa7 zEjuGI_I>O#mKo>#kNf-lKc6$_oX?r_KIeJA@ArM4@AFh8vHeV9D?Z?cp&S7po^xTE zU7cu>;AQBGELPDKqC3~_+;+aeQ_Ib3GVwiulPKqAhMFdZy48XQqk`hD_dNRDVwvT( zrv_RLVYTPF=Ca@JBDa5`SS^@17%kRPh+NN?6~i!^p;8+v)vkb`tQzyZ_nntg*J4Pb1qZg3&`Pdi#-l zXLWfkowPY6&m~SQcRzMoxNc(+Cl+i(B`A*;9<#g#5&T{7!!s*+mp`O9<){Lq^gc z5Zezrl2wR1?$=NUi25FX9rGMCHRAvl-mo>)ufx6CaO?MUXva$0h#2m12Xt@mZHgLQ z>O0o-Bx+X*+Te}!3<~8XSL7|;YOP=Ab9gZfCVKq!;8sC+?Z_`KyMlbo=g_PajzZQA zE?=2F^zBJhYx&`-Rt(*}$=q7_x+PP$`lHfQ8Tz9~sDpK|PgI0%OHtqvyS-pfEop}r;2)=Lx9!mbdOganx? zj17yehs|SS?N>P@!+oOULGwIi>6IAa<4|ty1dafXEV*tt zc<3qp*iEyZUm5N5+d}E=Es)O5T&vMm{-wZwGpKc{dc;BEO;xjwZ0Os-K84-e}eyK9%!EE5iR->M9oM&IEwNxe9}? zLM-U{bi8)0={lF(BJ&l_;XGpl`@04(~G7$*?=o19{uM zY+V@$|4fDX@TE6g9Ozc;1*IM>w~KnHbGYwR&w`iQoI32TIX~(UZeAw{DLI^u2K=Kh zQ8sq!U>8$EFh!$sI1b+2EJK6m#r6vNo00cP2zi8zjj;shP{vh)kTGbC73T8=-Ye3D zUfC9kZCK>~0|%|HC3rMbxR{hesrw{-iA+nmEaVt-iEe-fUQMQ-1p<;sN-oD_XHR)j zwN#PqF|jF@BWbI+nRZ3pWUxO-RaqJ1w%nk~-Jl_U;sl|vEch>G-@dJ24aMAG75aAk zv}Sjl7vs^jIM93ux_5geQ}vlkHhM&3nNcE0y9l|AGSVx2k1skGMlNU`G4R}{cA3j_ z*PLwSt=h_gzucQVt$({RTr4XPJaDeA1{P~ioV1tYF+%0OR;{GsJvsa7t*`4rQ4VpS zd>s5Q55ON;`*os2%dECoz*^~DU5#$Jt8ax1ya-OS>A2I@Oof3i7yPt-f2?oyyPo+n zLd!?wjM<4JwqQQI7kzy#OK%e1o?HJr@$-cO4QQTC2lD`>XIWvdJ)YI;CdJQgF?vnPR8j}=0o@=`tpa{JYLKyUPpFuNEaM}j2ysFJ;Q+OmBZ~y?f zx+gh_-7#{+*U^%oARS>&){C{?z3@@ zyEfq+ePS)Rr7T#ZaBa;8UNMrs;dcboK>>hH;&A!Y=Stt{Yo5QW%y+^@;h`#@54Q^K zZd-8l?aBfNP;5Zg^u$_~w6rt^n`@ajRsG@xH*L7Gs1W+!mkSxc7E(yD0wU#pvnE*< zvSKzRUqLU6p6%_b3tqoJ!RxK^qs?*f8{J49atPcKpu)F|>OwP=+CV%b9?m=}U*^4d z3)}Oxz&7AP6A$@{le4oPGx6SW4ocirV7oLcZkO3G-c4o34XN&;&jt?uOjR$B*xMz@ zgEu&?_Uw<3*u&F)SChHHovQ7fIdNrJN3{ky!$r&j|hnnkTsirUFL(GoIg*qHfDZgHZ6te;+j3a`S|Au}(L2k~vwf~>brQa=MwZ>Jnr{6?l4K~-v>qwA=JYtwPf?U@vFr9)~D7rTd9 zQ+!+AtlDpzv%)-_)4rFah*;yaOCMSi9prXWjieDnmZt=-K}9EKc_Qr;b1dbi;R^Ju zFUe2p9)H8&Af&Z$WnPvv8i$8kXXzt1E`_h+gyw2A#)4&8h{ncj_q z?&yIRq2u?`abO)^aXkK>2*a0EyR*G=lyn#*vO2D~NfWy>yNyc_t>CYwxzZfrAm=bQ zV8L?iFp~Y+z`k2rPPHL~uXEUdQl9tQCW|2SVp!$Gd_r-3G2^RbKl{ zuXtg;_>ALlwy@~|Fe&7^sg}37V+eFeY+IWggZIH3R9ll-Egv+?`R?9&*FEr|a()*L zftNeKlSN*^4jKdBHe1(lV4I_v#G6msV9VVv3rzJvPT;Cv_$S2F*@Y{*9CO-1wo0OK z_RX5#wD30js^r1Jo;h<~+Q+059qg6}0SJg`ORNQuL z>~^AW1uw9SU)h3$0n25h`{c{7=9qJ8nBR0imaQ(LzZkR6crU0&3 zlu`S_cNq3A{0?gW`!3Yx>`IDFiH>=(RRkgz2_K)29(xtP_gv^)IIg$17gA=P^sKbM z{u3|RW`My;N=p8U*Iv|&xEInqLsgLOq%n{^CxvBBHoYwiMV~1_J6gP3x$9mzxJ=

ohFh<_(AOi*{sLPB)tab(r6AG0l@=SYRhCVsz6|uK$gM(;%TV?b5dAPMAZg z8n?a}m|Mk%A!6Z`#ztK%ZG>xyGV6N@#H*!x*)CP?!<(;#J%L6iF0QG{fah^8U~q7i zWHWdnMIq_dDZzhQ?*7^b25|^HvzGF{c}ZYqV(?uelD)MRjJ%nB_5j6Lk#Xo`fUC@@ zMAkt+?=rmMnWML?*HwSnb1d2T4%k-vW+CCb42jR+?o3Aty;Z(wjy~v!V`5J(Y+v6X zl0AciDd6wC@CI>DF^z37Hc$vPgE^z6r8qe0D9h-vV+{we5@X1H_2H4*ojzwF1MB=C zt#m87cnc&Aj*Vp?zQ|1*Vx^c@?KvBem8`>sO^WRHB-VdKbT-I!N{|OXlXQQxzbK-- zKwY;wzy3@?9slW|ew_b;yEpvhbEVIu0k&tCzPLB9%^C;!kdKeM99SwBqK z0tp3WhZtIBvXF%b?7j0;9apB@2#IgGPK=K@toD`K?E;iBa3NV&DelbZ2|} z%a6`6I_Hus)l$=}0Lo6w8K1mrpg2m&ZS>H|-0{?m1c47DOD>Po+ooqUD{_X6@6nh4 zyd$Fi5EO50?oW<7Va!%kP(O4&9+ZpU&oiu9>CSiENLmA>R@WxMhi!+dXuI5<-_=e- z?Ck6ZQvstp<^{*eH<&u=&gg+~v(AcWHHfAG(d1Knn~lk$aQSDCV-qW`tZxb5+C74U zt%n{C#9c1tJb8NUdXq#`J2}%7^){b0wgMVVZ-l`25nP1p8n0NdN__ed=gC zO{&?WmBxPBdFf=EU74+hrD z978`TpWE7&;pc{B9{T%fi^8s?rGSE?YeDjmwAGX87&}E(rIHkyGf!Ia;IVA$^G${~ z(CnWF{_Opu_VDXgtBCjnmyMZ@O!j;2jYm$OTXLJxDQ%kc@f@wIp3tC)NmL0?9zE>) z|AgL3eaC^B?}|RkXG4w%$5NeY%jF;>STqr-cu~|ak%e~yRdJ)A!Os4l&-RYg&(FJoH6bId@$!2avl}6ZO6i2vACAV$H9Ae0W?3BM8%WadGazg( z`*qHb6oK|DT3dE4nbR?NN8sbW_6>>E8erKhllQ^rvMryn>HF^p?gNT6* z@O=-Mw~aVvSbx`(}{rH|9ECJq8ng-~6(4vH=O# zKY5)r#0Q`93S&A(UP`2QG9{0AnvVG5MxqoSTeZd~yhULd;qrI%^ud#JoK_RjFXc6-k|I**Bn$k$sD$HMQRJc)V{0jMNmRm907 zNEMZ5fAHCeJ#Al_oUp%1oR2`G3W?mHs0@GGKr_5NxFp4gD`FwCjX zMM~DWm44_4K}#|>h(@3&3PW@QBL$F3tj`;jYMprLk=}9IE>pyX1^~P&Sbbef?#|yY z^6YrK5Oxv&c@%)EF6DWAi0xQ_^fiz9wMG7!wYdYC~_q4s_y~C#t9b;)5p& zs2XCzldEhF1;nu@5=smG{f@zd_QvIG`q{6hp}vKFHE6xf(;?%M3NAEv0guQ6z}J0PYyF#9?8Fc z{(OW0eJ9^42CZJ}BTO1BBKP*y8T+x2M*(iGKTLgRN;Es#shD~t))u=ljR<{7SG)O; zy9$4^hZS&P8Tu;h@4Mn47BZ8fuy?B=&^s#^u1V)OJnmK zcxP>y5kB@*G7dkzJ7KI{jeRYRN}1}t3WHsvE1s(d21kq3aip6?P_Ooa089h_(ZFRV zv1JTRDILZAfx*fP*BKI&s~8d3Cfsjc5fLZW`qNqZPyp4-?euW@o(POIBO18V?YC$Y zUPEy@1X960%ejrf_3H37XX9C_`#j5$480m)wVC=uKUf{C$8BBFUeCKinyES0v(?9b z)O|}B&jxp}ZA2tN(@c4FESKaX3Zc}y$q}_Xt$eiZ7zLeBa%E#JVh`JsbuN6VajG{u zADkTD+Y0rVSdnMe-Oj#cw>P|@95?u#3R?M)I?k2(LKT4zK^~++G-gELqGaNwHJB@b zQlMMJxkD+SOmw74hpJJ2r8d&($Z(bB#*V)*bPr!wUAlX^!}MQ^P=dGR#=+iJnkiCa zej47N*hgpnGQ*Tg`JLgVCw0D!p)7sMD`=~DCpn~|w1s@4SIP+o<100(AY$>8t@Rh0 z{<@NCdY}x9yz*7Dec!~sBlmf-@O|s##t_vnG+BgppS~>)Qf1W-noFu$o-ee zOaO9!muv`9G?wl?5OhIC1)P6T*t-WCP%cKA__K4&dV&dO z-^$!m%KXG%BIEl`Q>fzi*7*=zo7px0vE91mMGX8Ig-CLp4kO+Mf2iPJBDtgfrgx4F zfEQCh%KhZ!K9^OrS6O<9#x>7Btx|LJ#qUgDaU4M?vGrVp7HB@Bu=Mko$YCj2Wu z4N~blh6)Sxpm4}M2ES*Y4)(owfCJmfg+h@wqg9SkN>|8GVW+mcc@cSWBhIF8ab=hF zL5RLv=>na_F-=7rGWzcuk|GWTOO2E*n=4#b^^7uE%nn$|zK7~kPd>puwvNq786c%3 zZ~|bL$T5z;pHP60Y__~?gf@i`MggLIeaDbXSIPrq@(sGk28gVw z)?j2Hc8%qnY|;|OGsci!n7|20R9FLK!A&X zBV7=H%Y1(T(b4D9e&5YnwSQw`-)kF(ps<~4YnPbb5oFSzX8C|QbU-=KN3h<#w4+ag z%sbIQm-fEGzrgCu?|g+XlzxZ!>zW`{v6m*KbP&i8R=x>jY$4VoQ;u=`vERW_@3Tai zEt2MkrCN6kNy`<;