From 6d4c069fe409c34a14a26b7f933b9b5c40ac66ef Mon Sep 17 00:00:00 2001 From: sigonasr2 Date: Mon, 19 Jun 2023 03:25:01 -0500 Subject: [PATCH] Implemented Battlecry. Debuff/Buff modifiers. Fixed bug where player can auto attack during a ground slam. --- Crawler/Animation.h | 1 + Crawler/Buff.h | 12 +++ Crawler/Crawler.cpp | 16 +++- Crawler/Crawler.h | 5 +- Crawler/Crawler.vcxproj | 1 + Crawler/Crawler.vcxproj.filters | 3 + Crawler/Monster.cpp | 52 +++++++++--- Crawler/Monster.h | 4 + Crawler/Player.cpp | 124 ++++++++++++++++++++-------- Crawler/Player.h | 11 ++- Crawler/Version.h | 2 +- Crawler/assets/battlecry_effect.png | Bin 0 -> 3051 bytes 12 files changed, 180 insertions(+), 51 deletions(-) create mode 100644 Crawler/Buff.h create mode 100644 Crawler/assets/battlecry_effect.png diff --git a/Crawler/Animation.h b/Crawler/Animation.h index 25f0f7b9..5a6051c6 100644 --- a/Crawler/Animation.h +++ b/Crawler/Animation.h @@ -13,4 +13,5 @@ enum AnimationState{ RANGER_IDLE_S,RANGER_IDLE_E,RANGER_IDLE_N,RANGER_IDLE_W, WIZARD_WALK_S,WIZARD_WALK_E,WIZARD_WALK_N,WIZARD_WALK_W, WIZARD_IDLE_S,WIZARD_IDLE_E,WIZARD_IDLE_N,WIZARD_IDLE_W, + BATTLECRY_EFFECT, }; \ No newline at end of file diff --git a/Crawler/Buff.h b/Crawler/Buff.h new file mode 100644 index 00000000..f5097d48 --- /dev/null +++ b/Crawler/Buff.h @@ -0,0 +1,12 @@ +#pragma once +enum BuffType{ + ATTACK_UP, + DAMAGE_REDUCTION, + SLOWDOWN, +}; + +struct Buff{ + BuffType type; + float duration; + float intensity; +}; \ No newline at end of file diff --git a/Crawler/Crawler.cpp b/Crawler/Crawler.cpp index d652faec..91747706 100644 --- a/Crawler/Crawler.cpp +++ b/Crawler/Crawler.cpp @@ -41,6 +41,7 @@ bool Crawler::OnUserCreate(){ GFX_BLOCK_BUBBLE.Load("assets/block.png"); GFX_Ranger_Sheet.Load("assets/nico-ranger.png"); GFX_Wizard_Sheet.Load("assets/nico-wizard.png"); + GFX_Battlecry_Effect.Load("assets/battlecry_effect.png"); //Animations InitializeAnimations(); @@ -291,6 +292,11 @@ void Crawler::InitializeAnimations(){ } ANIMATION_DATA[AnimationState::GROUND_SLAM_ATTACK_BACK]=effect_groundslam_back; ANIMATION_DATA[AnimationState::GROUND_SLAM_ATTACK_FRONT]=effect_groundslam_front; + Animate2D::FrameSequence battlecry_effect(0.02f,Animate2D::Style::OneShot); + for(int i=0;i<5;i++){ + battlecry_effect.AddFrame({&GFX_Battlecry_Effect,{{i*84,0},{84,84}}}); + } + ANIMATION_DATA[AnimationState::BATTLECRY_EFFECT]=battlecry_effect; } bool Crawler::LeftHeld(){ @@ -606,7 +612,7 @@ void Crawler::RenderWorld(float fElapsedTime){ for(Monster&m:monstersBefore){ m.Draw(); } - view.DrawPartialRotatedDecal(player.GetPos()+vf2d{0,-player.GetZ()},player.GetFrame().GetSourceImage()->Decal(),player.GetSpinAngle(),{12,12},player.GetFrame().GetSourceRect().pos,player.GetFrame().GetSourceRect().size,vf2d(player.GetSizeMult(),player.GetSizeMult())); + view.DrawPartialRotatedDecal(player.GetPos()+vf2d{0,-player.GetZ()},player.GetFrame().GetSourceImage()->Decal(),player.GetSpinAngle(),{12,12},player.GetFrame().GetSourceRect().pos,player.GetFrame().GetSourceRect().size,vf2d(player.GetSizeMult(),player.GetSizeMult()),player.GetBuffs(BuffType::ATTACK_UP).size()>0?Pixel{255,uint8_t(255*abs(sin(1.4*player.GetBuffs(BuffType::ATTACK_UP)[0].duration))),uint8_t(255*abs(sin(1.4*player.GetBuffs(BuffType::ATTACK_UP)[0].duration)))}:WHITE); if(player.GetState()==State::BLOCK){ view.DrawDecal(player.GetPos()-vf2d{12,12},GFX_BLOCK_BUBBLE.Decal()); } @@ -696,6 +702,14 @@ void Crawler::AddEffect(Effect foreground,Effect background){ backgroundEffects.push_back(background); } +void Crawler::AddEffect(Effect foreground,bool back){ + if(back){ + backgroundEffects.push_back(foreground); + } else { + foregroundEffects.push_back(foreground); + } +} + vf2d Crawler::GetWorldMousePos(){ return GetMousePos()+view.GetWorldOffset(); } diff --git a/Crawler/Crawler.h b/Crawler/Crawler.h index 385c0be7..79d6257a 100644 --- a/Crawler/Crawler.h +++ b/Crawler/Crawler.h @@ -14,7 +14,8 @@ class Crawler : public olc::PixelGameEngine Player player; Renderable GFX_Warrior_Sheet,GFX_Slime_Sheet,GFX_Circle, GFX_Effect_GroundSlam_Back,GFX_Effect_GroundSlam_Front, - GFX_Heart,GFX_BLOCK_BUBBLE,GFX_Ranger_Sheet,GFX_Wizard_Sheet; + GFX_Heart,GFX_BLOCK_BUBBLE,GFX_Ranger_Sheet,GFX_Wizard_Sheet, + GFX_Battlecry_Effect; std::vectorforegroundEffects,backgroundEffects; public: @@ -33,6 +34,8 @@ public: void RenderWorld(float fElapsedTime); void RenderHud(); void AddEffect(Effect foreground,Effect background); + //If back is true, places the effect in the background + void AddEffect(Effect foreground,bool back=false); void HurtEnemies(vf2d pos,float radius,int damage); vf2d GetWorldMousePos(); bool LeftHeld(); diff --git a/Crawler/Crawler.vcxproj b/Crawler/Crawler.vcxproj index 9dc9de45..88829222 100644 --- a/Crawler/Crawler.vcxproj +++ b/Crawler/Crawler.vcxproj @@ -164,6 +164,7 @@ + diff --git a/Crawler/Crawler.vcxproj.filters b/Crawler/Crawler.vcxproj.filters index 23ba71d7..d916e1c0 100644 --- a/Crawler/Crawler.vcxproj.filters +++ b/Crawler/Crawler.vcxproj.filters @@ -75,6 +75,9 @@ Header Files + + Header Files + diff --git a/Crawler/Monster.cpp b/Crawler/Monster.cpp index 10575b4f..31862038 100644 --- a/Crawler/Monster.cpp +++ b/Crawler/Monster.cpp @@ -57,10 +57,18 @@ int Monster::GetHealth(){ return hp; } int Monster::GetAttack(){ - return atk; + float mod_atk=atk; + for(Buff&b:GetBuffs(ATTACK_UP)){ + mod_atk+=atk*b.intensity; + } + return int(mod_atk); } float Monster::GetMoveSpdMult(){ - return moveSpd; + float mod_moveSpd=moveSpd; + for(Buff&b:GetBuffs(SLOWDOWN)){ + mod_moveSpd-=moveSpd*b.intensity; + } + return mod_moveSpd; } float Monster::GetSizeMult(){ return size; @@ -113,6 +121,14 @@ void Monster::SetY(float y){ } bool Monster::Update(float fElapsedTime){ if(IsAlive()){ + for(std::vector::iterator it=buffList.begin();it!=buffList.end();++it){ + Buff&b=*it; + b.duration-=fElapsedTime; + if(b.duration<=0){ + it=buffList.erase(it); + if(it==buffList.end())break; + } + } for(Monster&m:MONSTER_LIST){ if(&m==this)continue; if(geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m.GetPos(),12*m.GetSizeMult()/2))){ @@ -146,8 +162,8 @@ bool Monster::Update(float fElapsedTime){ target=geom2d::line(pos,game->GetPlayer().GetPos()).upoint(1.2); state=MOVE_TOWARDS; } - if(state==MOVE_TOWARDS&&geom2d::line(pos,target).length()>100*fElapsedTime*moveSpd){ - SetPosition(pos+geom2d::line(pos,target).vector().norm()*100*fElapsedTime*moveSpd); + if(state==MOVE_TOWARDS&&geom2d::line(pos,target).length()>100*fElapsedTime*GetMoveSpdMult()){ + SetPosition(pos+geom2d::line(pos,target).vector().norm()*100*fElapsedTime*GetMoveSpdMult()); PerformJumpAnimation(); } else { if(state==MOVE_TOWARDS){ @@ -163,7 +179,7 @@ bool Monster::Update(float fElapsedTime){ queueShotTimer-=fElapsedTime; if(queueShotTimer<0){ queueShotTimer=0; - BULLET_LIST.push_back(Bullet(pos+vf2d{0,-4},geom2d::line(pos+vf2d{0,-4},game->GetPlayer().GetPos()).vector().norm()*24*3.f,2,atk,{75/2,162/2,225/2})); + BULLET_LIST.push_back(Bullet(pos+vf2d{0,-4},geom2d::line(pos+vf2d{0,-4},game->GetPlayer().GetPos()).vector().norm()*24*3.f,2,GetAttack(),{75/2,162/2,225/2})); } } geom2d::line line(pos,game->GetPlayer().GetPos()); @@ -203,7 +219,7 @@ bool Monster::Update(float fElapsedTime){ switch(state){ case MOVE_TOWARDS:{ if(moveTowardsLine.length()>1){ - SetPosition(pos+moveTowardsLine.vector().norm()*100*fElapsedTime*moveSpd); + SetPosition(pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult()); } if(line.length()<=24*7){ state=NORMAL; @@ -217,7 +233,7 @@ bool Monster::Update(float fElapsedTime){ }break; case MOVE_AWAY:{ if(moveTowardsLine.length()>1){ - SetPosition(pos+moveTowardsLine.vector().norm()*100*fElapsedTime*moveSpd); + SetPosition(pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult()); } if(line.length()>=24*6){ state=NORMAL; @@ -267,9 +283,9 @@ Key Monster::GetFacingDirection(){ } void Monster::Draw(){ if(GetFacingDirection()==RIGHT){ - game->view.DrawPartialDecal((GetPos()+vf2d{float(GetFrame().GetSourceRect().size.x),0}*GetSizeMult())-vf2d{12,12}*GetSizeMult(),GetFrame().GetSourceImage()->Decal(),GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult()*-1,GetSizeMult())); + game->view.DrawPartialDecal((GetPos()+vf2d{float(GetFrame().GetSourceRect().size.x),0}*GetSizeMult())-vf2d{12,12}*GetSizeMult(),GetFrame().GetSourceImage()->Decal(),GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult()*-1,GetSizeMult()),GetBuffs(BuffType::SLOWDOWN).size()>0?Pixel{uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(128+127*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration)))}:WHITE); } else { - game->view.DrawPartialDecal(GetPos()-vf2d{12,12}*GetSizeMult(),GetFrame().GetSourceImage()->Decal(),GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult(),GetSizeMult())); + game->view.DrawPartialDecal(GetPos()-vf2d{12,12}*GetSizeMult(),GetFrame().GetSourceImage()->Decal(),GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult(),GetSizeMult()),GetBuffs(BuffType::SLOWDOWN).size()>0?Pixel{uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(128+127*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration)))}:WHITE); } } void Monster::Collision(Player&p){ @@ -311,8 +327,12 @@ AnimationState Monster::GetDeathAnimationName(){ } bool Monster::Hurt(int damage){ if(hp<=0) return false; - hp=std::max(0,hp-damage); - DAMAGENUMBER_LIST.push_back(DamageNumber(pos,damage)); + float mod_dmg=damage; + for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){ + mod_dmg-=damage*b.intensity; + } + hp=std::max(0,hp-int(mod_dmg)); + DAMAGENUMBER_LIST.push_back(DamageNumber(pos,int(mod_dmg))); if(hp<=0){ animation.ChangeState(internal_animState,GetDeathAnimationName()); } @@ -346,4 +366,14 @@ void MonsterSpawner::SetTriggered(bool trigger,bool spawnMonsters){ MONSTER_LIST.push_back(Monster(pos+monsterInfo.second,MONSTER_DATA[monsterInfo.first])); } } +} + +void Monster::AddBuff(BuffType type,float duration,float intensity){ + buffList.push_back(Buff{type,duration,intensity}); +} + +std::vectorMonster::GetBuffs(BuffType buff){ + std::vectorfilteredBuffs; + std::copy_if(buffList.begin(),buffList.end(),std::back_inserter(filteredBuffs),[buff](Buff&b){return b.type==buff;}); + return filteredBuffs; } \ No newline at end of file diff --git a/Crawler/Monster.h b/Crawler/Monster.h index fe67ce0b..1ee6a7a9 100644 --- a/Crawler/Monster.h +++ b/Crawler/Monster.h @@ -64,6 +64,7 @@ struct Monster{ float randomFrameOffset=0.f; float deathTimer=0.f; MonsterName type; + std::vectorbuffList; AnimationState GetDeathAnimationName(); public: Monster(); @@ -91,6 +92,9 @@ struct Monster{ void SetY(float y); void PerformJumpAnimation(); void PerformShootAnimation(); + + void AddBuff(BuffType type,float duration,float intensity); + std::vectorGetBuffs(BuffType buff); }; struct MonsterSpawner{ diff --git a/Crawler/Player.cpp b/Crawler/Player.cpp index 21956e34..5ae6d116 100644 --- a/Crawler/Player.cpp +++ b/Crawler/Player.cpp @@ -67,11 +67,19 @@ int Player::GetMaxHealth(){ } int Player::GetAttack(){ - return atk; + float mod_atk=atk; + for(Buff&b:GetBuffs(BuffType::ATTACK_UP)){ + mod_atk+=atk*b.intensity; + } + return int(mod_atk); } float Player::GetMoveSpdMult(){ - return moveSpd; + float mod_moveSpd=moveSpd; + for(Buff&b:GetBuffs(BuffType::SLOWDOWN)){ + mod_moveSpd-=moveSpd*b.intensity; + } + return mod_moveSpd; } float Player::GetSizeMult(){ @@ -93,6 +101,14 @@ State Player::GetState(){ void Player::Update(float fElapsedTime){ attack_cooldown_timer=std::max(0.f,attack_cooldown_timer-fElapsedTime); iframe_time=std::max(0.f,iframe_time-fElapsedTime); + for(std::vector::iterator it=buffList.begin();it!=buffList.end();++it){ + Buff&b=*it; + b.duration-=fElapsedTime; + if(b.duration<=0){ + it=buffList.erase(it); + if(it==buffList.end())break; + } + } switch(state){ case SPIN:{ switch(facingDirection){ @@ -123,7 +139,7 @@ void Player::Update(float fElapsedTime){ state=NORMAL; spin_angle=0; z=0; - game->HurtEnemies(pos,3*12,atk*2.5); + game->HurtEnemies(pos,3*12,GetAttack()*2.5); game->AddEffect(Effect{GetPos(),0.5,AnimationState::GROUND_SLAM_ATTACK_FRONT,1.33f,0.6f},Effect{GetPos(),0.5,AnimationState::GROUND_SLAM_ATTACK_BACK,1.33f,0.6f}); } if(lastAnimationFlip>0){ @@ -138,7 +154,6 @@ void Player::Update(float fElapsedTime){ }break; default:{ //Update animations normally. - moveSpd=1.0; animation.UpdateState(internal_animState,fElapsedTime); } } @@ -179,35 +194,62 @@ void Player::Update(float fElapsedTime){ } if(attack_cooldown_timer==0&&game->GetMouse(0).bHeld){ switch (cl) { - case WARRIOR: { - bool attack=false; - Monster*closest=nullptr; - float closest_dist=999999; - for(Monster&m:MONSTER_LIST){ - if(m.IsAlive() - &&geom2d::overlaps(geom2d::circle(pos-vf2d{size*12,size*12},attack_range*size*12),geom2d::circle(m.GetPos()-vf2d{m.GetSizeMult()*12,m.GetSizeMult()*12},m.GetSizeMult()*12)) - &&geom2d::line(game->GetWorldMousePos(),m.GetPos()).length()(game->GetWorldMousePos(),m.GetPos()).length(); - closest=&m; + case WARRIOR:{ + if(state!=State::SPIN){ + bool attack=false; + Monster*closest=nullptr; + float closest_dist=999999; + for(Monster&m:MONSTER_LIST){ + if(m.IsAlive() + &&geom2d::overlaps(geom2d::circle(pos-vf2d{size*12,size*12},attack_range*size*12),geom2d::circle(m.GetPos()-vf2d{m.GetSizeMult()*12,m.GetSizeMult()*12},m.GetSizeMult()*12)) + &&geom2d::line(game->GetWorldMousePos(),m.GetPos()).length()(game->GetWorldMousePos(),m.GetPos()).length(); + closest=&m; + } + } + if(closest!=nullptr&&closest->Hurt(GetAttack())){ + attack_cooldown_timer=ATTACK_COOLDOWN; + swordSwingTimer=0.2; + SetState(State::SWING_SWORD); + switch(facingDirection){ + case DOWN:{ + UpdateAnimation(AnimationState::SWINGSWORD_S); + }break; + case RIGHT:{ + UpdateAnimation(AnimationState::SWINGSWORD_E); + }break; + case LEFT:{ + UpdateAnimation(AnimationState::SWINGSWORD_W); + }break; + case UP:{ + UpdateAnimation(AnimationState::SWINGSWORD_N); + }break; + } } } - if(closest!=nullptr&&closest->Hurt(atk)){ - attack_cooldown_timer=ATTACK_COOLDOWN; - swordSwingTimer=0.2; - SetState(State::SWING_SWORD); - switch(facingDirection){ - case DOWN:{ - UpdateAnimation(AnimationState::SWINGSWORD_S); - }break; - case RIGHT:{ - UpdateAnimation(AnimationState::SWINGSWORD_E); - }break; - case LEFT:{ - UpdateAnimation(AnimationState::SWINGSWORD_W); - }break; - case UP:{ - UpdateAnimation(AnimationState::SWINGSWORD_N); - }break; + }break; + case THIEF: { + }break; + case RANGER: { + }break; + case BARD: { + }break; + case WIZARD: { + }break; + case WITCH: { + }break; + } + } + if(ability1.cooldown==0&&game->GetKey(SHIFT).bHeld){ + switch (cl) { + case WARRIOR: { + game->AddEffect(Effect(pos,0.1,AnimationState::BATTLECRY_EFFECT,1,0.3)); + ability1.cooldown=ability1.COOLDOWN_TIME; + AddBuff(BuffType::ATTACK_UP,10,0.1); + AddBuff(BuffType::DAMAGE_REDUCTION,10,0.1); + for(Monster&m:MONSTER_LIST){ + if(geom2d::overlaps(geom2d::circle(pos,12*3.5),geom2d::circle(m.GetPos(),m.GetSizeMult()*12))){ + m.AddBuff(BuffType::SLOWDOWN,5,0.3); } } }break; @@ -229,7 +271,7 @@ void Player::Update(float fElapsedTime){ if(GetState()==State::NORMAL){ rightClickAbility.cooldown=rightClickAbility.COOLDOWN_TIME; SetState(State::BLOCK); - moveSpd=0.7; + AddBuff(BuffType::SLOWDOWN,3,0.3); } }break; case THIEF: { @@ -269,8 +311,12 @@ bool Player::HasIframes(){ bool Player::Hurt(int damage){ if(hp<=0||iframe_time!=0) return false; if(state==State::BLOCK)damage=0; - hp=std::max(0,hp-damage); - DAMAGENUMBER_LIST.push_back(DamageNumber(pos,damage)); + float mod_dmg=damage; + for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){ + mod_dmg-=damage*b.intensity; + } + hp=std::max(0,hp-int(mod_dmg)); + DAMAGENUMBER_LIST.push_back(DamageNumber(pos,int(mod_dmg))); return true; } @@ -345,4 +391,14 @@ void Player::UpdateIdleAnimation(Key direction){ case LEFT:anim=CLASS_DATA[cl].idle_w;break; } UpdateAnimation(anim); +} + +void Player::AddBuff(BuffType type,float duration,float intensity){ + buffList.push_back(Buff{type,duration,intensity}); +} + +std::vectorPlayer::GetBuffs(BuffType buff){ + std::vectorfilteredBuffs; + std::copy_if(buffList.begin(),buffList.end(),std::back_inserter(filteredBuffs),[buff](Buff&b){return b.type==buff;}); + return filteredBuffs; } \ No newline at end of file diff --git a/Crawler/Player.h b/Crawler/Player.h index f422225a..e6d1026d 100644 --- a/Crawler/Player.h +++ b/Crawler/Player.h @@ -5,10 +5,11 @@ #include "State.h" #include "Ability.h" #include "Class.h" +#include "Buff.h" struct Player{ friend class Crawler; -private: + private: Class cl=WARRIOR; int hp=100,maxhp=hp; int atk=10; @@ -45,8 +46,9 @@ private: void SetZ(float z); void SetPos(vf2d pos); void SetClass(Class cl); -protected: -public: + std::vectorbuffList; + protected: + public: Player(); const static float GROUND_SLAM_SPIN_TIME; vf2d&GetPos(); @@ -67,6 +69,9 @@ public: void UpdateWalkingAnimation(Key direction); void UpdateIdleAnimation(Key direction); + void AddBuff(BuffType type,float duration,float intensity); + std::vectorGetBuffs(BuffType buff); + bool Hurt(int damage); void UpdateAnimation(AnimationState animState); Animate2D::Frame GetFrame(); diff --git a/Crawler/Version.h b/Crawler/Version.h index 6a607d36..e099f840 100644 --- a/Crawler/Version.h +++ b/Crawler/Version.h @@ -2,7 +2,7 @@ #define VERSION_MAJOR 0 #define VERSION_MINOR 2 #define VERSION_PATCH 0 -#define VERSION_BUILD 31 +#define VERSION_BUILD 45 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Crawler/assets/battlecry_effect.png b/Crawler/assets/battlecry_effect.png new file mode 100644 index 0000000000000000000000000000000000000000..f243c0d50b08c944d712d4589517c04672815e31 GIT binary patch literal 3051 zcmY*bdpy%^8~=^vm}AH!&FRTulhZ0?8OAnxoS8WvlT!yxVWowH(6)${5R=e&#GEIR zLrD%X%9G?=JzCfz9`&yGeV_Mv-aoGI=X-tc>%Kqt=X2fn_4%edIbftDR3rcZkiucn zE&u>x2)LV=hyVvFtn&rJ)o^QTC!Do4^i0g@6X8*z0H8y?L`7n|P^#Vjt}G2j=cCxO zVxD(lX&Hy@6t4%|t5@`jHEiZd(oMB=4YHFWJ3DW5S0-PttCp@#f^L9jKF%IU)M7Xw9G_b$XkA!&gB(%{006+X=Ade5=WoSQ#0A z?i2H>?Pd#14GjUAmP=pEKjry!&r_2-w_e5<9qy2zSzUB6KQ*)?eP6Rd>z_Sl+}}D( zgl+YV995x8Yj9Yjp}w--is@ObGvfK~5d-~##=})*5|O{(d^}V%=0Wo*H_OzyC2Os! z5#r{A7FT*wAHm4S4ka?OES5HA=V;H|!J557&z;oG62Dk%yk*|LLnq$+Q?7WeyTQU1 z^v=Mk=FQjj+URjdfNOH546!}0Vz`A%U{zerCbvgV&$LuLBX!J68}b;UJqK_|bPTv|Fv`GB5Xs~=swDDr%_SPh#2oML| zu`EtgxQ>W2N6G0bMNY(#gU~QQ94=aT+#Qz_pY!6FtGj%tns(iKwVlR*F!Wyddy}=8 z{P}|Ql7k;}hPqfL4x>EqkGWl@}tnKDjpgZGP)cUqRhh z8f@nK>W8jx^NahaW?{?Pgs0oYA7@0cCqLTh9f{KzJbTrTwy26<(#9={QHIe0gaYj1CxoM zt4?(PE$sUEu_wn%#puB^HqO)~`eDWN>U3AC;C8?<6)0f^4DR>E>lcy(n(LyvcgX_s z5;9On)Qg7N0AldvhO8Z>d$yur0JaYzLaXtA}j zRx~#*U-P%Affz6O$gHyS z+4K{aLnf%Vo_YmW+|?Aq+T(2?2c<@PPWr;f++_YqH;q|FE^v@mWr^j&UeF`@+L21- z5?%mIy~?bnG(){Az7eIQ17gev<|v|>YeOk6&nR_C4`S8mPI{14nETUVbF#dbh%Kpg z!mh~Z-KEm0$!?ELm`OftaZAv`zcLT{0Y&*E4<$}iWF%B-^?_LW=8~+>0MEU2GmdRB~+RL+m_~zv}ZO7SCPAz z7-}Or(!hO8C6@>B==Y@+sKQ?nx*rDGdh{Rrcf!0lf6$Bo#wv}gpxJuwm%pt zlzLcWr(^j+49hWm)Wqr7tW1&5$tI_)G+2s5wM9oB=u4LPjGaN=7s<5sB@`o1MJ6^J z#|zt*R>OwW8W9%W_a!s?2J2gRJ4$#s^I2WKomT3u3da&T$7qX+QGKei17nJ8YtGtc zxYU3b^%j#8T^>o`9%VK6{-z!5>3*DUA4`jVflP%%O8H!l!P{_yFaW?fMA5mkmPaYha#DR{{o-I z7T1fO+1bE7DmOs#))L zdFzJpvWbakipizzU;@N<&vx(l>2hc9-)efNk_P!<9;nd=nbld}*KYWiJNM4`++go0 z`ZNez9ThHR`qz!y{@`=DuO9ffjt8IaT--F#&50OLqLz=2QDev6I}e0+leP-{ptve& zZU#;wo89*ISov=3NM%=R5KU#qZ|YQ0^6=N~qZ+3z^%*Xq@@Gc~GKN_RPA56S$4je! zyvjoX;(}30SHx2D{xD7?{KN&5RoY%o3P3A)mUmDt6YM;27cB?%e4`_~%t-yTk)U2S z92RNxZRD3EDzvisW}l`-+Mf)i$@cz*=MCKN1+~j}qTk6~LBHhjYdFcr+7plZW^OlZ zK6o7y?VAzR`H6{4iLG(K0D(&cXE#%=A?HpDG|r7Np3b3-}ng!ykpUczW^@LxgClN3B?q; zDL(&)zfWNM!OX9#S82i(J9OX~aau#eBu5XUNZ2QW`_CW4UJp^M>ygjOrQ%{@cvG)3 z7VI5{M)mkN9~G(?8SgGjbm6#aY8lcNN=6;Mn0PA#S=%@W`d!8l{{>?7@e#D29YV5Rg`} zDJVZ;rePv5E~Jmp>9(t}Te?fr`lZBudeap-l$VMfD0{@XCu3^G z7LIK2keTW=;%6U<_)yDA_;M_};(~dl203NtPM@_Ut>s8dTaOsFNOe4s_LT+I)Nl3^ z9ydRN+wzh{fhkU!3Z|Vdnu77qZ#BKOsO4RW=(#@19q}x@8CAhimXQT%nTk2+_uLv@ zGKp&G3*n)}pz0VP&N_g%!2!Ocb}X$hLDN!KhG#X&;NpV}Q2GOWLY%Obq&x?o5CY0k z(t23K6Fhc^RVBux3nM{O8`!x|7n=aS_#dAzrk3Hnto}AarZ(gn4V12pCP@3YKW1-b zYNH{e{)yk?*CF<4#t&|q=d^eUIc>+&qpuZ(xi{zTKfl4p=jNGHVc_CEVY0CxA_j#~ zR<3*2qA05cvl5e|$i=272V*~|g5>Q2JJ_k-ZxrMpZc1k#BP|LKAQ^sL@aT-RoqE@s z1cu~HJCBlZ&*i@r7H=&I1e*wo*g%(7(x8x+UodxmASk^0{CeSz`nH~_{yW_xKFgnx zk|*P{*a&Hq6<{Zptrp+yZIX@W_Qmu!;M1& literal 0 HcmV?d00001