From e1d50f1f275b5162e3b158ff6315828579b26531 Mon Sep 17 00:00:00 2001 From: Vasyl Horbachenko Date: Tue, 6 Nov 2018 02:33:38 +0200 Subject: [PATCH] added missing assets; convoy strike event --- game/event/__init__.py | 1 + game/event/convoystrike.py | 79 ++++++++++++++++++++++ game/event/insurgentattack.py | 2 +- game/game.py | 3 +- game/operation/convoystrike.py | 46 +++++++++++++ gen/armor.py | 16 ++++- gen/conflictgen.py | 25 +++++++ resources/tools/generate_loadout_check.py | 1 + resources/ui/events/air_intercept.png | Bin 0 -> 3231 bytes resources/ui/events/attack.PNG | Bin 0 -> 637 bytes resources/ui/events/capture.PNG | Bin 0 -> 697 bytes resources/ui/events/convoy.png | Bin 0 -> 804 bytes resources/ui/events/delivery.PNG | Bin 0 -> 1263 bytes resources/ui/events/infantry.PNG | Bin 0 -> 910 bytes resources/ui/events/insurgent_attack.PNG | Bin 0 -> 824 bytes resources/ui/events/naval_intercept.PNG | Bin 0 -> 688 bytes resources/ui/events/strike.PNG | Bin 0 -> 1163 bytes ui/overviewcanvas.py | 3 +- 18 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 game/event/convoystrike.py create mode 100644 game/operation/convoystrike.py create mode 100644 resources/ui/events/air_intercept.png create mode 100644 resources/ui/events/attack.PNG create mode 100644 resources/ui/events/capture.PNG create mode 100644 resources/ui/events/convoy.png create mode 100644 resources/ui/events/delivery.PNG create mode 100644 resources/ui/events/infantry.PNG create mode 100644 resources/ui/events/insurgent_attack.PNG create mode 100644 resources/ui/events/naval_intercept.PNG create mode 100644 resources/ui/events/strike.PNG diff --git a/game/event/__init__.py b/game/event/__init__.py index f9a147a6..11025c6a 100644 --- a/game/event/__init__.py +++ b/game/event/__init__.py @@ -5,5 +5,6 @@ from .intercept import * from .baseattack import * from .navalintercept import * from .insurgentattack import * +from .convoystrike import * from .infantrytransport import * from .strike import * diff --git a/game/event/convoystrike.py b/game/event/convoystrike.py new file mode 100644 index 00000000..e7418769 --- /dev/null +++ b/game/event/convoystrike.py @@ -0,0 +1,79 @@ +import math +import random + +from dcs.task import * + +from game import * +from game.event import * +from game.event.frontlineattack import FrontlineAttackEvent + +from .event import * +from game.operation.convoystrike import ConvoyStrikeOperation + +TRANSPORT_COUNT = 4, 6 +DEFENDERS_AMOUNT_FACTOR = 4 + + +class ConvoyStrikeEvent(Event): + SUCCESS_FACTOR = 0.7 + STRENGTH_INFLUENCE = 0.25 + + targets = None # type: db.ArmorDict + + @property + def threat_description(self): + return "" + + @property + def tasks(self): + return [CAS] + + def flight_name(self, for_task: typing.Type[Task]) -> str: + if for_task == CAS: + return "Strike flight" + + def __str__(self): + return "Convoy Strike" + + def skip(self): + self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + + def commit(self, debriefing: Debriefing): + super(ConvoyStrikeEvent, self).commit(debriefing) + + if self.from_cp.captured: + if self.is_successfull(debriefing): + self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + else: + if self.is_successfull(debriefing): + self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + + def is_successfull(self, debriefing: Debriefing): + killed_units = sum([v for k, v in debriefing.destroyed_units[self.defender_name].items() if db.unit_task(k) in [PinpointStrike, Reconnaissance]]) + all_units = sum(self.targets.values()) + attackers_success = (float(killed_units) / (all_units + 0.01)) > self.SUCCESS_FACTOR + if self.from_cp.captured: + return attackers_success + else: + return not attackers_success + + def player_attacking(self, flights: db.TaskForceDict): + assert CAS in flights and len(flights) == 1, "Invalid flights" + + convoy_unittype = db.find_unittype(Reconnaissance, self.defender_name)[0] + defense_unittype = db.find_unittype(PinpointStrike, self.defender_name)[0] + + defenders_count = int(math.ceil(self.from_cp.base.strength * self.from_cp.importance * DEFENDERS_AMOUNT_FACTOR)) + self.targets = {convoy_unittype: random.randrange(*TRANSPORT_COUNT), + defense_unittype: defenders_count, } + + op = ConvoyStrikeOperation(game=self.game, + attacker_name=self.attacker_name, + defender_name=self.defender_name, + from_cp=self.from_cp, + departure_cp=self.departure_cp, + to_cp=self.to_cp) + op.setup(target=self.targets, + strikegroup=flights[CAS]) + + self.operation = op diff --git a/game/event/insurgentattack.py b/game/event/insurgentattack.py index bf4d373c..e98b1e7f 100644 --- a/game/event/insurgentattack.py +++ b/game/event/insurgentattack.py @@ -39,7 +39,7 @@ class InsurgentAttackEvent(Event): killed_units = sum([v for k, v in debriefing.destroyed_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike]) all_units = sum(self.targets.values()) attackers_success = (float(killed_units) / (all_units + 0.01)) > self.SUCCESS_FACTOR - if self.departure_cp.captured: + if self.from_cp.captured: return attackers_success else: return not attackers_success diff --git a/game/game.py b/game/game.py index d177271b..6b4502f0 100644 --- a/game/game.py +++ b/game/game.py @@ -56,6 +56,7 @@ EVENT_PROBABILITIES = { # events randomly present; only for the player InfantryTransportEvent: [25, 0], + ConvoyStrikeEvent: [25, 0], # events conditionally present; for both enemy and player BaseAttackEvent: [100, 9], @@ -164,7 +165,7 @@ class Game: def _generate_events(self): for player_cp, enemy_cp in self.theater.conflicts(True): for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items(): - if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent]: + if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent, ConvoyStrikeEvent]: # skip events requiring frontline if not Conflict.has_frontline_between(player_cp, enemy_cp): continue diff --git a/game/operation/convoystrike.py b/game/operation/convoystrike.py new file mode 100644 index 00000000..5cb76f55 --- /dev/null +++ b/game/operation/convoystrike.py @@ -0,0 +1,46 @@ +from game.db import assigned_units_split + +from .operation import * + + +class ConvoyStrikeOperation(Operation): + strikegroup = None # type: db.AssignedUnitsDict + target = None # type: db.ArmorDict + + def setup(self, + target: db.ArmorDict, + strikegroup: db.AssignedUnitsDict): + self.strikegroup = strikegroup + self.target = target + + def prepare(self, terrain: Terrain, is_quick: bool): + super(ConvoyStrikeOperation, self).prepare(terrain, is_quick) + + conflict = Conflict.convoy_strike_conflict( + attacker=self.current_mission.country(self.attacker_name), + defender=self.current_mission.country(self.defender_name), + from_cp=self.from_cp, + to_cp=self.to_cp, + theater=self.game.theater + ) + + self.initialize(mission=self.current_mission, + conflict=conflict) + + def generate(self): + planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()} + self.airgen.generate_cas_strikegroup(*assigned_units_split(planes_flights), at=self.attackers_starting_position) + + heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()} + if heli_flights: + self.briefinggen.append_frequency("FARP + Heli flights", "127.5 MHz AM") + for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])), + db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)): + self.airgen.generate_cas_strikegroup(*assigned_units_split(dict), + at=farp, + escort=len(planes_flights) == 0) + + self.armorgen.generate_convoy(self.target) + + self.briefinggen.append_waypoint("TARGET") + super(ConvoyStrikeOperation, self).generate() diff --git a/gen/armor.py b/gen/armor.py index 070da766..92d1d89a 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -36,7 +36,7 @@ class ArmorConflictGenerator: return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_SIZE_FACTOR) - def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, to: Point = None): + def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, to: Point = None, move_formation: PointAction = PointAction.OffRoad): for c in range(count): logging.info("armorgen: {} for {}".format(unit, side.id)) group = self.m.vehicle_group( @@ -45,7 +45,7 @@ class ArmorConflictGenerator: unit, position=self._group_point(at), group_size=1, - move_formation=PointAction.OffRoad) + move_formation=move_formation) vehicle: Vehicle = group.units[0] vehicle.player_can_drive = True @@ -53,7 +53,7 @@ class ArmorConflictGenerator: if not to: to = self.conflict.position.point_from_heading(0, 500) - wayp = group.add_waypoint(self._group_point(to)) + wayp = group.add_waypoint(self._group_point(to), move_formation=move_formation) wayp.tasks = [] def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point): @@ -109,6 +109,16 @@ class ArmorConflictGenerator: random.randint(0, self.conflict.distance)) self._generate_fight_at(attacker_group_dict, target_group_dict, position) + def generate_convoy(self, units: db.ArmorDict): + for type, count in units.items(): + self._generate_group( + side=self.conflict.defenders_side, + unit=type, + count=count, + at=self.conflict.ground_defenders_location, + to=self.conflict.position, + move_formation=PointAction.OnRoad) + def generate_passengers(self, count: int): unit_type = random.choice(db.find_unittype(Nothing, self.conflict.attackers_side.name)) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 63336147..bf169aaf 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -321,6 +321,31 @@ class Conflict: air_defenders_location=position.point_from_heading(heading, AIR_DISTANCE), ) + @classmethod + def convoy_strike_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): + frontline_position, frontline_heading, frontline_length = Conflict.frontline_vector(from_cp, to_cp, theater) + if not frontline_position: + assert False + + heading = _heading_sum(frontline_heading, +45) + starting_position = Conflict._find_ground_position(frontline_position.point_from_heading(heading, 15000), + GROUND_INTERCEPT_SPREAD, + _opposite_heading(heading), theater) + destination_position = frontline_position + + return cls( + position=destination_position, + theater=theater, + from_cp=from_cp, + to_cp=to_cp, + attackers_side=attacker, + defenders_side=defender, + ground_attackers_location=None, + ground_defenders_location=starting_position, + air_attackers_location=starting_position.point_from_heading(_opposite_heading(heading), AIR_DISTANCE), + air_defenders_location=starting_position.point_from_heading(heading, AIR_DISTANCE), + ) + @classmethod def frontline_cas_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): assert cls.has_frontline_between(from_cp, to_cp) diff --git a/resources/tools/generate_loadout_check.py b/resources/tools/generate_loadout_check.py index 575b8519..33bce161 100644 --- a/resources/tools/generate_loadout_check.py +++ b/resources/tools/generate_loadout_check.py @@ -1,4 +1,5 @@ import os +import sys import dcs from game import db diff --git a/resources/ui/events/air_intercept.png b/resources/ui/events/air_intercept.png new file mode 100644 index 0000000000000000000000000000000000000000..1b66f0f6658a7cd90c0d6fae632fc7d0c44ea4f5 GIT binary patch literal 3231 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 RBfJ0r002ovPDHLkV1mgz4YB|L literal 0 HcmV?d00001 diff --git a/resources/ui/events/attack.PNG b/resources/ui/events/attack.PNG new file mode 100644 index 0000000000000000000000000000000000000000..eb7169bfff23528e5f491a99206f0514761ae3c2 GIT binary patch literal 637 zcmV-@0)qXCP)N2bZe?^J zG%heMGBNQWX_Wu~0s2WqK~z{r?UX-ALU9ns{jaFc|N49;_y_+D34yo>@rnkmz@dbw zaB+|Zhqh=4Czqh8p{8nRh@hnx`?A2@z@_tU%k zop-7j3(dv)KwwcUweJ^aO*!yxZ-b z^Z9&h9*<|=YPGH+)_69X^*2A(9#lJKFc|KjO0vLywOA}?kRuecjRdEOL}CI-ib%2` z2*MJH*Xy0f-B77i@=~mppi&dQ%#*}oF&p_BjRsFADt^cDom@#Y8qG7ual7GgSgcek z9DJRF7fj(rH&EqZ4|=`+nsl?-d=LFizH22$qwyGpuHd!Pp-^bJUaxE5?+pAND>xJb z|5i*U(-jI@PNh=4uv?SQ=LMZkrx@ff1f6}%<#K({l_?gB99sMYm0GVDAZv~VZF}LIt(^TRI61z2KIpNaqi(|Mhxi@-DCK+ z=nn?NcuV(K#S8rYBE{|sT1Rwb6bZ|CQJGG(^Gol~0kq38#2 literal 0 HcmV?d00001 diff --git a/resources/ui/events/capture.PNG b/resources/ui/events/capture.PNG new file mode 100644 index 0000000000000000000000000000000000000000..98ab3cd9ca14b2f50a0613edaa60c36dcbea54ad GIT binary patch literal 697 zcmV;q0!ICbP)N2bZe?^J zG%heMGBNQWX_Wu~0yarRK~z{rV*mkFRaFBH4vv#7EG$p?`1pG2>+6}o67&K<%P#?` z|1c~jCKe46ryBtJ{5Oz-J3vH4qzxnv1Io(E#=N||Z9s?a;o{=D4hk(Go+%+A;Q^Fp zgs377$ji%XK$#2-K*Lu9DX`^?jEujerKQb5;ND2m;@f#Q-%OoWwT|+}dd4OUJ&d$!l zqN1WfKnHFCdgvcaElnJNj6vqHv9W=o6stVV9Y6?=I$+cRJUl!%fHVXQgad%(!FeDJ z0i$VP)B&Roz~=yfY!G;=DD zh5&310M=wDfK})Ql$Hw?ps%kl4OBM=sQ)jJi(&~j40P~!pl5P`Y(z^6n*)@TlypEe zu|P&f#uOMLhk;BK3t<>kEdw38LrO|Y4ai3Y*c?DE05<0wffg+QI_4HI8lM8&j+cSO z?i64n#Tcpt6O@*gQq2Km0cwT-Szt5-fO)bQNP!&y%zn>-T{UU21ib*z(vLt276Si) z9fwMgINboSaqt>Q!5ttdC`T?MiV}NPkCy)thDqR6}CLl6&1V%N2bZe?^J zG%heMGBNQWX_Wu~0-;GnK~z{r?UOrbQ&AYlZ{82uq)qc|V%|++Fd>h&keY`P zP@{vAES-#TFoP5vibExX3Z+#j5;utnict`US`~^Gd{Jp}a1iY1;NqkNum5wo4NB2c zTpaEX4(EL5@qPdEzvogM92^`R9RGsH<1wIfB$G+UPLDP*Iy!pD>2x|Q7R$k0E+=O; z@0V7qWn_ncM5ohzMo0gxLBwhF`=`_CxY=Qk<>h5jAP|_rt&aJ8zK~X{-BSRZIES8L zZf;Jvw6xThNF=1;aQLvvWO7o$V6c=*rOsrt+4DxDv519TmZj5alU}cnkBp35%;)o` zN~KaF5{b+j42C@g*lf0*fQ5wxNu$xwOifMA;8Q&v8ymYxA@K#W*x%oO-EOykmq;Yv zhK7dPSUgGQVe7sohF|2<<;&?pHTtux_e?qNQvLB7S zjV=UkQP^}B>&)$NlZQyU25WB%1cGbi;N;}w%D}+DYpDL1$z)bhxETtCn%LWfy-kP1 zaZ9CAZNR~ID7uJ0qmA(^665xZ%`%AB6$-^0#Cmoc1p!wf_AErT z2L}h!n989gn8jbg_dODgM(u3KfVl|}V8dNNbR2HD*pNcusLSQ@upvXGQt5+$6L3Vq iPH}K>aB%z&P}EP_`@o7wU5FL{0000N2bZe?^J zG%heMGBNQWX_Wu~1Z+t}K~z{r?UzevlvfnTXOhW#CNq;U$>i0^YZ8+t6HBmhAp}HZ z7A1*>s0ejah-4Ho=t4s4;6@j|DhOIo5w#Qot*EUhV z&g6VwzWF*aGl@bM{^5|h=iKwS|9kJbUmR~S6B-*E!xa@3%WG8nJRZ+srQef0!G3R0brNjFE|~d9he05XwJSl?lJ! ze+>IZ9VQ`X00yp7@Uz<5+GbVH9VGrj3TS9(h^Wd$QBlz;3K-L&iOR~#HR4lRTKci7 z<__q;!=ZbmLo1vV@E!KUIyNydFc6R}!~S)Df4^^dcsNf@*@Kl#5a@;uO>p)BA*R7WS}Otgdr(#XL0el}hydGkD2MZcy#Qf=Ios`es|efNoH7kF1-({3 zKm7iF3#C(X{R{2|WF!>E(oKw*hbh-DW)l%Ukjb?o!kw~2Y zdV70aurx-Xl)X1Z7_b*W=Uk}na&ST|ft7O7} zprxf{0rMur4!fIF?QrlAov=D*E9n^E`ys~p>az=pPl}yeb57GI8`2)DbGDL>f!T!q za8c9083tw(`pI+woPoXRCS@w~W`J98Jl!OMUS`imxW}ud(=ooEZl0Y1`pP8pk8lpw z(Y!t+*%5S4tUf6WFk!#dD1u|M{qlwEWrY!WpycY1Xxu*6$^iHLXBO0stCLrp) z!%nhqTU%Q_atjPy3IlYel~n!&krl}6xD?vomzUCT5TS@)q%G6acN%j^Ji!*!GJhFu z_NK&x{zFzj5n)+uBz{Po7c6dzj7^M=juwrKjQB<5W0gsp?LkD^+uIk(SCD0{OG7xu z$H$ZZH<09}-49l;i7gh3x$&9qf-?bDR@s9HOG2+snGrB>*6s^yU#dAX$WzTqE*Sx2 zJD}M7E_f9i-iQ Zj+Y4d>Y#J?qZ0rC002ovPDHLkV1jNlR676w literal 0 HcmV?d00001 diff --git a/resources/ui/events/infantry.PNG b/resources/ui/events/infantry.PNG new file mode 100644 index 0000000000000000000000000000000000000000..a3dd2b38506d306c9f1d4e29e438fa1fb38f2052 GIT binary patch literal 910 zcmV;919AL`P)N2bZe?^J zG%heMGBNQWX_Wu~0}Dw+K~z{ry;nVGQ&AL7^RH>z{IzM5kfaG|LPDC*qCyJ|jY+2v zP>3Nz9TEq_DXnkq%X~)jq%T-fPgl zx3uKugTr_3_a2;++eFjgODd zlHN6VJf6#-b^>0n_a^CG!|?F%yjH8-hRYiahVOpA|AzRai?G>jt03NFGOZU11&ydO zMlP2-J~A@GS*_MLYPGt(1vrqaqobo2kceJ*kp<{iBJgXt?Z5}k<#OEvvq2ybxCl}V zyl*<4?oTR}ioowI(&@C*Xf%FTfL<#O4Kb!ULavxK@=45(y!y`K8R9ui-}_RUwTRV%85o1C0HddHlanu?(c z7LmvfmP$c)=^3``X`|7w#pChQXjoSf_(3=vUPRy6DwRqhynA#^Pfv#sWDl+^0VKD9 zq2z$Ch+3^?L1M?G<u(!AOD_mIu_~pOB?14ZnkSu|rp`i^hdmw@PAXx%v$1lL_ zfy49`BufDLXE1x9&Od=<3E*t}YPDMIr($1U-w%)w6eUJ~yWrDwoSU2T!eZu=g)%O_oR`&XC3wI0Mf?)Oglt z?RLAb(YmLnhexA2K^jw7ES7Z;B_K?_nVFdct;ujE(xfp32FyngKR7r@4N28(Hci+T zA%U^6G4=^yz}y8<=er{+LQ+C$V%jA|g&{(r&^(C8%rAb;R2Tw%;2emb zn3y;(DncbJ(IG4ri+)LB2Yd_O0`d4f<3vTJ!fp5ph|gxTr$mJjqS5F{YCtH(FPTiH zeYwDPm?XLL2nK_fQ0oN2bZe?^J zG%heMGBNQWX_Wu~0<}p*K~z{r#g@x!6Hye#^M1cy=Ao+)DG>=0B2l!0D<4H%hylA2 z3R2!M1 z@AtnW6jO~ppYN4iQx2!oc@^0(?lPH7o4z%YZnfg3e8P?FdU)ghP5O(9n1GTA<$S*Q*S?+nssE;FCH z?m6D{@?I4w1--)mf&kX5*kf4(*FxJ{sWG|;U>XMzKxiS zyJW4uM|&~6B>0yfM%JLo*g!dES)J+nlHfop$Fi-1<#q`9291WY%odp8Uk!bmC9w+E zdDo{>skB||-2yy(`E}}RIz(dnB^HZK$_<4=VT_k98m?O=!86C@*v-bYQ}5aw)O3ai ztTvjr(DYa9O@dfTWe;o|_YjjgBeBScY$NW2Aqg=*qjlN=+pU*b|AercK!^4bxi$z4 z?+$QKZ`wz-TJ?34pd2zHqlY8`E>e;Mde;cxaQp>%F9O}{Kl_gW0000N2bZe?^J zG%heMGBNQWX_Wu~0xd~IK~z{rV;BXaU=)l3T%f+bo+&LYjTOqJ9{^g)B_kt~$iu^P zN>fwQ4k}E05E~mSr>(7>EG#TM0m%Ii!ph3Zozc^z;n&_VzaO z^Yhb!@j-MzK!B&0mzM;L55gWE9==c`@B>g908%Ip07e1a0XjN5o5aP%|4T?n{0GrM zybPE|8j<**Tyq0xc@9h+2m=%JAEKfF7j5`>gvi3OoUEArO`;( z2Iv4iWHF$j0YF*`Sqzwa3`9gk>VXdV1LT7p#mdV1*UZe!0?38{pvJ2}3hruj4AlGx z=#VqyV4x>nqMIivDcJ+05kN^v$p#oaD2ix>fu6Y;8X5{JQ&0iWRoy@et0rn;Mn=ZJ zz^JqavM~X$_TvCnKo@~bteUBX#l*yFfi!ji%s!x0pGiv$v;&lwN5Lo<1)~587#IMm WRvLN2bZe?^J zG%heMGBNQWX_Wu~1PDn)K~z{r)s|aqj6oE~cenS;?zY>@Hnzz&Z3&ImLnWw0g$G3? zF0BM1Bt$~IAR-}cTp9#Hh>(a!@gO7~!~+r1CLVZnOWY!ELE0dsTHVj@%*4*B`{tr&bwQyI?9%{}Gw`C97h>x(lpGnGT)NK;djx1^+GHseP!GBQ4x z`F(u9O6>d*FxDZ)kpYTre>fa2GD8YPIqZODG-L}{cn1!Ar7PQW0_{(8N+^rScWcHOM05=$ zdwfqb&L9)(QBs%}G!qBa)zy9`e8JmZqEC93YC6wq_47LakGTNFL!BxL*++T|h@V@P2qMqbJQ1`gxh-e_TbGgoo0`T#Je z3pH%a)Q>`YdwT}i+8ApC>5?r#J`v2W($dm#<`WG&6I>kAHIoo-Qr%819Ho9%%c6#N zL{(K)7$vFi)=UhDY7f@rJ9Kqc(4<&`yqZtQqe(Nwgh z3u+qe^nu^;8VYGAi{BuveNbvK5&EBy9-{9`^?4pOa74X`Ci2w&c+&~?dkaoH