From e486c8035083e554cdab558dbda32855b7f681ac Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Thu, 12 May 2016 12:38:35 +0200 Subject: [PATCH] Got something working now --- Moose/Client.lua | 4 +- Moose/MissileTrainer.lua | 268 ++++++++++++++---- .../Moose_Test_MISSILETRAINER.miz | Bin 114675 -> 114890 bytes 3 files changed, 217 insertions(+), 55 deletions(-) diff --git a/Moose/Client.lua b/Moose/Client.lua index 12d3b9888..be60463fb 100644 --- a/Moose/Client.lua +++ b/Moose/Client.lua @@ -12,7 +12,7 @@ Include.File( "Message" ) --- The CLIENT class -- @type CLIENT --- @extends Base#BASE +-- @extends Unit#UNIT CLIENT = { ONBOARDSIDE = { NONE = 0, @@ -48,7 +48,7 @@ CLIENT = { -- Mission:AddClient( CLIENT:New( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) -- Mission:AddClient( CLIENT:New( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) function CLIENT:New( ClientName, ClientBriefing ) - local self = BASE:Inherit( self, BASE:New() ) + local self = BASE:Inherit( self, UNIT:New( Unit.getByName( ClientName ) ) ) self:F( ClientName, ClientBriefing ) self.ClientName = ClientName diff --git a/Moose/MissileTrainer.lua b/Moose/MissileTrainer.lua index 852a7fe89..eaed63155 100644 --- a/Moose/MissileTrainer.lua +++ b/Moose/MissileTrainer.lua @@ -37,14 +37,34 @@ function MISSILETRAINER:New( Distance ) for ClientID, Client in pairs( self.DBClients ) do Client:Message( "Welcome to the Missile Trainer", 10, "ID", "TEST" ) + Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) + Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu ) Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) - Client.MenuToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesToAll = true } ) - Client.MenuToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesToAll = false } ) - Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesTrack = true } ) - Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesTrack = false } ) + + Client.MenuTracking = MENU_CLIENT:New( Client, "Tracking", Client.MainMenu ) + Client.MenuTrackingToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } ) + Client.MenuTrackingToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } ) + Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = true } ) + Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = false } ) + + Client.MenuAlerts = MENU_CLIENT:New( Client, "Alerts", Client.MainMenu ) + Client.MenuAlertsToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } ) + Client.MenuAlertsToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } ) + Client.MenuHitsOn = MENU_CLIENT_COMMAND:New( Client, "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = true } ) + Client.MenuHitsOff = MENU_CLIENT_COMMAND:New( Client, "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = false } ) + Client.MenuLaunchesOn = MENU_CLIENT_COMMAND:New( Client, "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = true } ) + Client.MenuLaunchesOff = MENU_CLIENT_COMMAND:New( Client, "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = false } ) + + Client.MenuDetails = MENU_CLIENT:New( Client, "Details", Client.MainMenu ) + Client.MenuDetailsDistanceOn = MENU_CLIENT_COMMAND:New( Client, "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = true } ) + Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = false } ) + Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = true } ) + Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = false } ) + + end -- self.DB:ForEachClient( -- --- @param Client#CLIENT Client @@ -54,9 +74,21 @@ function MISSILETRAINER:New( Distance ) -- ) self.MessagesOnOff = true - self.MessagesToAll = false - self.MessagesTrack = true - + + self.TrackingToAll = false + self.Tracking = true + + self.AlertsToAll = true + self.AlertsHits = true + self.AlertsLaunches = true + + self.DetailsRange = true + self.DetailsBearing = true + + self.TrackingMissiles = {} + + self.TrackingScheduler = SCHEDULER:New( self, self._TrackMissiles, {}, 0.5, 0.05, 0 ) + return self end @@ -68,18 +100,39 @@ function MISSILETRAINER:_MenuMessages( MenuParameters ) self.MessagesOnOff = MenuParameters.MessagesOnOff end - if MenuParameters.MessagesToAll then - self.MessagesToAll = MenuParameters.MessagesToAll + if MenuParameters.TrackingToAll then + self.TrackingToAll = MenuParameters.TrackingToAll end - if MenuParameters.MessagesTrack then - self.MessagesTrack = MenuParameters.MessagesTrack + if MenuParameters.Tracking then + self.Tracking = MenuParameters.Tracking + end + + if MenuParameters.AlertsToAll then + self.AlertsToAll = MenuParameters.AlertsToAll + end + + if MenuParameters.AlertsHits then + self.AlertsHits = MenuParameters.AlertsHits + end + + if MenuParameters.AlertsLaunches then + self.AlertsLaunches = MenuParameters.AlertsLaunches + end + + if MenuParameters.DetailsRange then + self.DetailsRange = MenuParameters.DetailsRange + end + + if MenuParameters.DetailsBearing then + self.DetailsBearing = MenuParameters.DetailsBearing end end --- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @see MISSILETRAINER +-- @param #MISSILETRAINER self +-- @param Event#EVENTDATA Event function MISSILETRAINER:_EventShot( Event ) self:F( { Event } ) @@ -96,68 +149,177 @@ function MISSILETRAINER:_EventShot( Event ) local TrainerTargetDCSGroupName = TrainerTargetDCSGroup:getName() local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill - self:T( TrainerTargetSkill ) local Client = self.DBClients[TrainerTargetDCSUnitName] if Client then + local TrainerSourceUnit = UNIT:New(TrainerSourceDCSUnit) local TrainerTargetUnit = UNIT:New(TrainerTargetDCSUnit) - self.Schedulers[#self.Schedulers+1] = SCHEDULER:New( self, self._FollowMissile, { TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client }, 0.5, 0.05, 0 ) + + if self.MessagesOnOff and self.AlertsLaunches then + + local Message = MESSAGE:New( + string.format( "%s launched a %s", + TrainerSourceUnit:GetTypeName(), + TrainerWeaponName + ) .. self:AddRange( Client, TrainerWeapon ) .. self:AddBearing( Client, TrainerWeapon ),"Launch", 5, "ID" ) + + if self.AlertsToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end + end + + local ClientID = Client:GetID() + self:T( ClientID ) + if not self.TrackingMissiles[ClientID] then + self.TrackingMissiles[ClientID] = {} + end + self.TrackingMissiles[ClientID].Client = Client + if not self.TrackingMissiles[ClientID].MissileData then + self.TrackingMissiles[ClientID].MissileData = {} + end + local MissileData = {} + MissileData.TrainerSourceUnit = TrainerSourceUnit + MissileData.TrainerWeapon = TrainerWeapon + MissileData.TrainerTargetUnit = TrainerTargetUnit + table.insert( self.TrackingMissiles[ClientID].MissileData, MissileData ) + --self:T( self.TrackingMissiles ) end end +function MISSILETRAINER:AddRange( Client, TrainerWeapon ) + + local RangeText = "" + + if self.DetailsRange then + + local PositionMissile = TrainerWeapon:getPoint() + local PositionTarget = Client:GetPositionVec3() + + local Range = ( ( PositionMissile.x - PositionTarget.x )^2 + + ( PositionMissile.y - PositionTarget.y )^2 + + ( PositionMissile.z - PositionTarget.z )^2 + ) ^ 0.5 + + RangeText = string.format( ", at %4.2fkm", Range ) + end + + return RangeText +end + +function MISSILETRAINER:AddBearing( Client, TrainerWeapon ) + + local BearingText = "" + + if self.DetailsBearing then + + local PositionMissile = TrainerWeapon:getPoint() + local PositionTarget = Client:GetPositionVec3() + + self:T( { PositionTarget, PositionMissile }) + + local DirectionVector = { x = PositionMissile.x - PositionTarget.x, y = PositionMissile.y - PositionTarget.y, z = PositionMissile.z - PositionTarget.z } + local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x ) + --DirectionRadians = DirectionRadians + routines.getNorthCorrection( PositionTarget ) + if DirectionRadians < 0 then + DirectionRadians = DirectionRadians + 2 * math.pi + end + local DirectionDegrees = DirectionRadians * 180 / math.pi + + BearingText = string.format( ", %d degrees", DirectionDegrees ) + end + + return BearingText +end + + --- -- @param #MISSILETRAINER self -- @param Unit#UNIT TrainerSourceDCSUnit -- @param DCSWeapon#Weapon TrainerWeapon -- @param Unit#UNIT TrainerTargetDCSUnit -- @param Client#CLIENT Client -function MISSILETRAINER:_FollowMissile( TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client ) - self:F( { TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client } ) - - local PositionMissile = TrainerWeapon:getPoint() - local PositionTarget = TrainerTargetUnit:GetPositionVec3() - - local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 - ) ^ 0.5 +function MISSILETRAINER:_TrackMissiles() + self:F2() - if self.MessagesOnOff and self.MessagesTrack and self.MessageLastTime + 2 <= timer.getTime() then - self:T( Distance ) + local ShowMessages = false + if self.MessagesOnOff and self.MessageLastTime + 3 <= timer.getTime() then self.MessageLastTime = timer.getTime() - local Message = MESSAGE:New( - string.format( "%s launched by %s: %4.2f km", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetName(), - Distance - ),"Tracking", 2, "ID" ) - - if self.MessagesToAll then - Message:ToAll() - else - Message:ToClient( Client ) - end + ShowMessages = true end - if Distance <= self.Distance then - TrainerWeapon:destroy() - if self.MessagesOnOff then - self:T( "Destroyed" ) - local Message = MESSAGE:New( - string.format( "%s launched by %s destroyed", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetName(), - Distance - ),"Tracking", 2, "ID" ) - if self.MessagesToAll then - Message:ToAll() - else - Message:ToClient( Client ) + for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do + + local Client = ClientData.Client + self:T2( { Client:GetName() } ) + + + ClientData.MessageToAll = "Missiles to Other Players:\n" + ClientData.MessageToClient = "Missiles to You:\n" + + for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do + + self:T( #TrackingData.MissileData ) + + for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do + self:T( MissileDataID ) + + local TrainerSourceUnit = MissileData.TrainerSourceUnit + local TrainerWeapon = MissileData.TrainerWeapon + local TrainerTargetUnit = MissileData.TrainerTargetUnit + + local PositionMissile = TrainerWeapon:getPosition().p + local PositionTarget = Client:GetPositionVec3() + + local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + + ( PositionMissile.y - PositionTarget.y )^2 + + ( PositionMissile.z - PositionTarget.z )^2 + ) ^ 0.5 + + if Distance <= self.Distance then + -- Hit alert + TrainerWeapon:destroy() + if self.MessagesOnOff and self.AlertsHits then + + self:T( "Destroyed" ) + + local Message = MESSAGE:New( + string.format( "%s launched by %s destroyed", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetTypeName() + ),"Tracking", 2, "ID" ) + + if self.AlertsToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end + + MissileData = nil + table.remove( TrackingData.MissileData, MissileDataID ) + end + else + local TrackingTo = string.format( " -> %s launched by %s", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetName() + ) + + if ClientDataID == TrackingDataID then + ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:AddRange( ClientData.Client, TrainerWeapon ) .. self:AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" + else + ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:AddRange( TrackingData.Client, TrainerWeapon ) .. self:AddBearing( TrackingData.Client, TrainerWeapon ) .. "\n" + end + end end end - return false + + if self.MessagesOnOff and self.Tracking and ShowMessages then + local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, "Tracking", 1, "ID" ):ToClient( Client ) + end + end return true diff --git a/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz b/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz index e971eab9dd73dc800c111328ee56f68ae5b16fe2..aa99ee89791f756cfd0943a160c18a849a8f0294 100644 GIT binary patch delta 8723 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
  • q}(3 zO}@M~)Sfwe7I>2`_{SN_dHUIeGx2w6b`G^(Yai^N`~VD*1?)9lJjl$tU#;hgKliO$ zwOLwtf7Rj7?z(_a$ex;;zNVG%Ii4X5?4G4@^F+91}>ga0^E2S*6~{?FC1z7O9hD6O^)$-Kn66lwx3 zwi1XbmRn*1`1aW+aeymyw?WWHT#rgE`ozQJK!gD0=ZP$m312Xh=%OJJMWs$d42X# z0bspL7exQleVk)JQ>y)w@o=?qgUP&&7f8U9i5J8@5{3GDl>0{d}kW~L<#{2a7 z|ISk)FEOYQ;2;pcJ_rE8|4m$OhK{Bdb}mk)PG_rQ-_r##g17iI{55$JkwW9?TJ6;| z%61!-XDjml2%BZd2Kisl)JHmAkZ*26xLS~)nP^ofkB+8t83^lEf( z!HGY+&x<&kPpSfyg~MQ7i05VIU#-Edww_E^UEG($>w#--HJQ??d{l`S`0iZ@6l=X= z4X&=|IRx9qhOMc>aIoIVJl%vwEx4qI+|^6Zr@MOf7xzZK#Ys|E(=qvSFz}C&l0R23 zcl=IFY$_B{U<90>i&M63#w2U0t{cMQKkjBZt2Gx&nqhUj+;_#qPEKyGUw)gIedT!1 zoql5RiuY=vUVBd|7RWazaJsvzykET=8v_!;7j=uOJKT(P^S3h{^NTlzM^4nrmscL82>ZIiKoRG}c{e`Y5`F)Skf zk;2X{e;)*oEEFvMugOx3Lqva63k9gw6kpM7k(mS8W2Y$o!@A<<%Z~#=DDKbTOCpfM zf8javu5Fl95)!a%|5*R~P2$Q#*OUK3mkY9>0JiZV|M-!k2NmaGkXqK$< z$i?Psg1tn8zy!YI%Rt9 zT}Q{h3({Ou?x-T^U^|3OFzsM1;Q#f@#ZV`hIv86rQ5P7k;{SVi_&1dQRfs{L!yK^x vIZ9NN0s9;4|NkoepGD!x>0MyB`2V#%{L4;TwDSK4=%y7dEfna zzum8^yQfa|b7ZQ!&r@|~#zIi9{-Atlf}hSn2T{>tfP*+hdj1}@Ei9^;a}gsU?)4NL1JPVJ;$1a8K>)i`nP=PpaOyq7_#E|Ac37^{S!f?kY^ARws>=K2pA}o7NmW38 ztkShnqy0hsx=E}KE&P)$M5813+Vyx z@{~cf;3A*S2E~8WW+w4%WSG`@-&Jb)g@~;)o-~7%&l0Ju>!w()xi*vMr8Iko0#&XU zH4EPA7pnFl{1?1|I_*hOcbtoMUb^^Qbt4>HI`kq5-e@zmsVRU5)4A>nI{?YNP6Cmd z%W^Q}^)9Lrd=1k2=md>W|Koe$4-O4Rpmun(-|Ik6XvVY8}h zwJXMDvC6#CVyi08uerq6iQ@h=*xJJriGYA3AD{I_f(ora%$PlPpxNrM50mkBwMI^3 z^#*L=N#OB^cLiXPAF`noRcl0WmuZt)WPh=%V~dxDhEMd>42{7Y)e##V9?~}aN`jp#~$x1FN6 z_cHPRyq9_gQ-^hu1eGE@*u4<{l{D@k0Y3P{dzVDc=3StA#XgVvQb4bCBK@*oM<0U_ zRIop0DIhO;H4&dWF$Y_As$#qjDO{I~c!Ff4(EU2#+e_vA>=Ja%XikvGs-SOC>gX_# zM+g7>ZLtx11Nm(HgX)Jl75{b1pwmRc4P$M98aUA$%tKU9gu}E?O0D|cq@>k_iybh>P4Qv$HJYGK3Jt8&viJL) zeLnaYoIVj~PpH==FOr6q*2+wkWv$EX#DtD@Q9oniaBx06u$E1eQPvb~2%b zCbf23HrxTb>L;o+(caGqmS#{me$S4E7#LsE0$>3F*{xJoSrqEy*y?C`+puNh6)9@) zp{EN;=(;}d_&cV(krii)ao?(|CVd<6nHnZWc+`a1-UEOeI}h)z1&N-O>$Mfe;>+zrXI-J@c^2-NiRUejf z0;M;GDGXZtg!`Hl*&fZ6&OxuW+kNMz){ZPt{q(NJc5Bt!gSyZ4u%ASYf6Y#;5XR*{ z*-ehvU6kBVJldqFHU;SLGh{FAjkrQQ$@>Zsa2RRYvFfD}ajVf|?MCH~Ia!fj<^9Y* zBFj674KR7P*RQ?&flE)2dMsl%?h$0e0S&R2zkSxj4hG9UWkNb-(Y?Id`I>kP5|_ z*H6+24ey@k$FxiI1T%Hq8gPwROJiov|?;Rxs(*ed5&FWG%+&~1hc=kxV=Kfz83!uhxpw__X_b)oJ29%Ih}?+ z(Oahi-`!eicYE9K;{5!JA&LV2K*B}9&WtG~aZ%t=F(9CS{gNd<`e&~68au0`$fY5m z-88=p=`u}JAZKvLDke zg~m;53|NunI+xE@kCT6_9_Q!$@y!#mEts>i6|*vwICeqxcB4<^QwdVYrktey;IXS( zZ1ASU%=hSh9s3%2feuLx5P=&?Z;40oGq2LDFY|J+R{N~jaF<3mlR?<;mk*`xyqGaF zUobuc+_NkMr!Xc|CDYef16KJ=+U_(ROEs6wlK`HYmf#dypQq9JTDiEbJG*+-rMVIw zx@Ck$#`b~xM!#C1qt@crH{SMSbUR(_&1&H4kAtq@w7Y+%n~W)#1jFK+!-jM8Sq^Ii)M<`Cw{*WZlf zb_%>}^X7SPr1W*ZOR+R3)#fx&AeGpr)S)^_;Gp_)j)^#Rx^B2_YITSIms_d7r}iVg!zD z*LIeh7|m%?QRZ9@w=CJlMgc{4vdbG``CtJ3n*iJKQ%p^$eKeC~409!DqzJ(wMg%fFlhfu>&$tR3`c=Q^YLl-b19>1+$Xcq+6Vk#=sR}QExOvL z!3EjK$ zyZB*AdEajfyL3$$inF@YoCp_+4q=g{6ys9kxusKukG*xCSS^ z>AT)a|41Ps`iYAM%N8lI%FjcY(dmHGFqeMp^|*l&C}3(AinCX0mCQ+ABjwYlZNm%{YY zl9pM7908_(E~bD#51Xcc?sETdaYA37kN*Tdb3D}Q(U0#_Ik>l)AH724*hivc8OS|2 zJT~}X-PPLAPeLL7mgh0sqT#S1@{=Eom+Q{{TUV)5)j6T88-Brsh>07}3|}%XVDL&% zY6sF?A?PCWi(7USdhzGkR)ljSd|3r9 z&M(p}<~CeJlJjHX&k&>(#SepgvPu@)RDF?&{REa>@LI+6`R8pFt$8tPrUjKh@`YC{ zdm*COgvui|LP{mXvh8;O{8hWXat=ih+`5bjP%GFW8OAvOj8G96%cGQq8Oxq8bMeBac0!amJPkrjZ|dyyPDPwR<~G;@8vWXc z_s8*KmP-ZL#eHv3LZK?`89AMtK?HfT|wENP^s2=!hz;sLV2Vb1| z2ue=HLG#dEOk3+HV3F7nJ#F?JMNn8VxtM#s8O7r#%mgrFp`79UQ^B)OzAgA8M*367-M zc!~2_2RW0Ml(`MClxt*J56l{*8_OxK+MMbwG}Zeo(3L;-0zV{{PcE077NSkgmn-Uf zh_ZDo!CWGlvNgM&anH$9eBK1KO^(KIq7(aCi}2<{2hGE zXG?#dPR3)xE&OS=o8?apSZw;J4&RydZ5z8a7U8M;!nuA{EVHm}W6iMr_GrwRM95SL z{JBTuR2l&gmP@`bdn*~ekudtuW$jX?SL*{PLay-{U{VC{2!|Hm#Eq?b@;W*hLn$*0 zxUwqZwjr)J(GlD7(+42V)B6SeTiuc+@*)}^YPuKfVz^F8=1z`peFpFAi)a&C%`oO> zBg%IDIOR_=iug2E$4T*9X!)*CEZ1*2iAqVSw{?N-&+c*rsWkz7-%ChNxZT1OsxO0O z)oUdl6Ktm5tQ|Xu^pR1#no=>OUxBw`(gM53WZ!4R?*(YfVA@Z#o^6_n z@NW#b*>uSs>acFC)}DXeu1;-ds;DThOOAE2{hXaLYD=P!u%g!ds$~q(?mcE|8hD{? zUzZzD^DE5eMvrYkvbx%b(oTr1jf|>bNq9=(x6XZiq@W0ZW^c1fKS+%m^u_vLY_{&P z&hiV*wX3xLQr7>n*?_^q5z?OXn>JM-60*AAQ88=)ZV5a@9bB({yj76UUR6@EeM@`9j9rMOriWcI%M;cW{VmYKFEUdNk*`b09rk_#i|3U%J0--TKP zu94xm(6aS4thfh67SIpCs^#MZDHQgV#nTHC&!^wl>WFz~nTisu3nzh?OT zB)41k+1`*qZY0Di@XqqJAwO95LT4~Bq23`MdkyxqVK_awz!I5mILB|Cw2rJz2;aI= z{wrRPiKojR^&fkO`~GVp->)qn6=+U%9h@0YX|wmdLO|L?7)Q+WB`{dpqx#xE{sa8m z@B$&iE+bVT^oB+L9(xqXn?ngLr&g0>U>U(2Cqrq0TURv`?Ir155M~l$hUMOlyi;-A zN=Dy0jjccgy{$0YZD%K1;{BWL#AS-TbQYr#02fyfDt>GlCkJksB4>ca+)vSvN+wV> z3%ETpG1&%pU%fkVUP=Q=O8eI*l3O#skC+@{b88*}M69`Vg-<}&E{EVN5-E>Bfhi6( z3{C{o{8u;YADcD~M}G(GLHD@~z7lk1|E{lgBQ4NE(LRd9J;@g(nx%UgHL70Y3I*_PkU*0x$?I%gG9+-xx!pXery9SdFjDj-@(RO$xIHq{AZDXb74+P~r8a3ojs zu+nl6HVHT~99F5?CcR3Pyq^6kBI@;rF#O*3w_H&BgOSb!h<3K?Vis3uEHsGB>i z1sh(%7sZGnbyHYI74o3F1;9*L=TR@rRa%9KDyo;+h@&=k^qN z90_Ze>*|Tf)I>>pO*xH4KFZl=Ejd*Tq*l{lU8GuwXJOk~BsWMfMots_x@{vr{%B-T zb05VTLix~;kaXUEnwE<=z>>`!Gw#e(NJW|PZ#b7=RM5`IRjUH6BCVa~P!!Z*m#)Jd zc--5EUMQBv{_r3jE90#tQy^v=Z*11KuZ(>eK^gYeTK^a5=kXU8bonoi#V8KLKy7#q zY->mUroSGXTAnZutBS)7ni&!7o?SCb(w<&X4#?C80=^DcJjL7+8gFIdchOvCn4+kY zxui{;SJK(t9K@XubqaKk0qyf^`u?kFSLk^uACONX)-wZrT=u$=oBhGJi5&&FcKfYwPzvC+ z^=tCD;jPBixxbtU?Xsm?RPOTnO+XGkx?blkm`dd9BDuqZ3ohUD7ODur;&YR=Pj4ZD z9jPu^yi;5q4RV9~P|#L11F5^^MLUifB&do=->0UKX}vw(=&VH;Mh)Jk&N}`wa(_JO zr7gK}+a5nRegy>>9}pt)+}}F#4Z!p+NGTjiHK$CcZwX1Ht70rXckwEV{**e=;Nnz( zF6GntC+fBJAYnIR(UdHyiZUGjTc6IYU&^K0yqt@PZ%_9%lp*h+OyY&r1maAhJ4u+# z36ll(WW@qF{j+KQU+QB}DTz6wy3hd6Rm(w?3dbHq>UE&dtPf+DC4BCK54D%kj6vdt z%z0#ofRNIR6^8VoLh%L}_>*+TCfSmhg2wN1tHA(=B^{jzTOY!$6(OpYTW8@;VMtVA zg0;Pg$~=LwhxM)hUC61Fbnhj~MhjCE)n~ktBLwNwpx~rl^_k`eKC|J&wu7#x%kCPd zh3!2(WDwvhG12?($F+#zm%)Ad0nFsfcXkVT;AIcZKTTZ}tY3>SX6$^T*Yw8Cxh35d zFfB~+U4NjVk)xI)aQPoTD-oc~`KHhWmI*k)XD-P8h_ZkWq8pL*kT_GRYg5FIPP4P2 z)9X7T+HxGv#F&bwLS!Ku5Riv;)AHyt<7#)YtQ+d9Vtcux$CJfqE}=DF3h@Z`%E0!`_q0n+56|GqNp%R{mBsz zCtKjaaP0NYEd{w5Ww1umnb9x#9Vi?|ai6Dp4^zh#nb1^zHzX&F2zm5z32M)s7k={$ zojbq2{f@hq{W6akMj8&=iE9q=q!mBs3G=ln+YLUmZqAN?*SzeDx7e+8*^^!5&sL(x z0v!e_?#JsXNOXS=5#$~%SZ`j>KSnH$?E(;e9C0_xoyEke8PHhTaIQoijf_L9 zzSj@tY!=JqM;%ns#TNQCwTX-d?{KM0s^ANzfEZG>+O5EvRo?^+BTQ{pO=lFgVoAx+ zit_Ad>Fby(3^Ja^uY9|_6imJ_*M(X9OLurHMtu%e)Sb_xP1`FPI+d;swd zB<&khrd4TDh0yQ;2XRcvu_m_RtV7CI#(LQ~ddQ{w8NSe}Qrfh^V7}6UmEzlDEl1$9 zMbHX*cFX?Jn!8GflWh>J!HfN_Se+yub2InAByD>6J%zSg?Lmjq0Rh4>zEHDb+H^U5 z+h6sIU-&{dy@8aOEXGo{Wm?uyJcDW*K| zKA7}^949EdmPL~ZJ3eeo&z7lf(*1xe>ULHT$AW-DO00B#&)QgwvDG*Ym{JHKW66EZ zw5Rzuw7wC~=X}ddsHKpio(M&oQNX>b^ObcRXqdkZC z3b7t1vy~7J4O|2omyG$=@T|dEo8= zKxLOcih|ga+#g(ZB}AmZWl1_m079t$nC#?TnrF3`M&0Vq zm`$2-d3XdMmuFUEo2EPp24ET{AnVyUTd%aaD%BQ#K_M~3<368W(ruVfzRtZqlnvyQ z=tnA45Xvk1r=5uZ(JY3>4qrWjl&_wi#c@aP)KZJ_KJMHNUl%ptMM1LMz^Gov=nGJ{ zby*e!!mt#LEy4-7mHNged4_OOcUvsDChEP0MhloIU2cvbt5>GA@kPBfa<2{n7vswRX+_~R@F@~xti=;C)4ob*!O{QB}gKtHY zk?-0Hvt45Aa51eXjY%k-*yE9BNo(xtd{L4hsIj5lvzT{6h3#O!m} z65O=vW!3cjdQV>dw5awtq@M)1^)j+97Lx%!;^&n<3V|PU7haD7n2OcNM)U4TQ=b;T zFwhAufx4uHxk&;%nv5?X{P$v4PhJhifk7l05n5-k_CFp0zlGQ~BZWibNNw&Lpq->S zIYiV2+05se9ZkongYhhqk8u$ds?j-o!5Ov>_76G3q!64&h`QfJ zdofvV$>UFSE%YKDsiPI;b~xtjfi8EAc3c|nZ+|Y{MmO79dBsBlC!E~FMt z%snv}be*APIWp!nv0TjM^Ol72R{~V@;9R(Fx2gKUt*$&+UVBefEPN{VI5gK%axv$y z=dDh)8JE@?C9l*^75|CUGk$rFdnK9r?-|M&U0LXcLsvg?8krAypKj`OxaAe5fhYNaWWAAfQs{cTw@GkexrOiOxE(gYR-l?})HcFK6^~YLj2S z+6&_Up9}&G!Xsl4;NYavBs<^<0Eblxg2?Ue8Y#O|0wcvNtAq$lc7nAxij5e9XpULR z3G_PRDyv@tYH3(56Hb(k)Me0W(=yU|Jn0_$KH;uEly`jmq-=c~;k<6({kz>GFq^?W z!*&uDRf!90p?PG4O(6LE=^7q8+>==iat`v zG9vR2dO8nbRJlosfg}VEEd+&7-lo8qdj{=V6WJf=Y|HAOa@>(aDKJKuIlt2Uy^TA~ zLMZ>Q_)mnqWfI(Yr})wTc*&DQP7JY2li>Zw1vcmlmP{v7!7H>7JUjIeE2i;-HDG)`N`5#kWRQ^4!56-u`>zY@ykH&gFSW&uT9~ZlcR z_;!T<=U;@p9(Y|u$~^cqjXZc_7Ih`~zvuQpwD#X0-~KY)zjOx&Hb3s0i)-@Nx9 rfO<1L-2brPe_Q>(_rnwp4&xt)nh8;