From 1655fe7b51391f24f05444bf97e90d060670400b Mon Sep 17 00:00:00 2001 From: sigonasr2 Date: Fri, 12 Jan 2024 11:51:07 -0600 Subject: [PATCH] Frog animations setup. Frog tongue bullet setup. --- .../Adventures in Lestoria.vcxproj | 5 + .../Adventures in Lestoria.vcxproj.filters | 6 ++ .../AdventuresInLestoria.cpp | 5 +- Adventures in Lestoria/AdventuresInLestoria.h | 3 + Adventures in Lestoria/BulletTypes.h | 12 +++ .../Chapter_1_Creatures_Part_2.txt | 23 +++++ Adventures in Lestoria/FrogTongue.cpp | 86 ++++++++++++++++++ Adventures in Lestoria/Monster.cpp | 4 + Adventures in Lestoria/Monster.h | 1 + Adventures in Lestoria/Player.cpp | 8 ++ Adventures in Lestoria/Player.h | 2 + Adventures in Lestoria/Version.h | 2 +- Adventures in Lestoria/Wizard.cpp | 5 +- .../assets/Campaigns/1_2.tmx | 10 +- .../assets/Campaigns/1_5.tmx | 10 +- .../assets/Campaigns/1_7.tmx | 10 +- .../assets/config/Monsters.txt | 12 ++- .../assets/config/gfx/gfx.txt | 2 + .../assets/items/Frog Skin.png | Bin 739 -> 4435 bytes .../assets/monsters/Frog.png | Bin 904 -> 6459 bytes Adventures in Lestoria/assets/tongue.png | Bin 0 -> 513 bytes Adventures in Lestoria/assets/tongue_end.png | Bin 0 -> 550 bytes Adventures in Lestoria/olcUTIL_Geometry2D.h | 7 +- 23 files changed, 192 insertions(+), 21 deletions(-) create mode 100644 Adventures in Lestoria/Chapter_1_Creatures_Part_2.txt create mode 100644 Adventures in Lestoria/FrogTongue.cpp create mode 100644 Adventures in Lestoria/assets/tongue.png create mode 100644 Adventures in Lestoria/assets/tongue_end.png diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj b/Adventures in Lestoria/Adventures in Lestoria.vcxproj index 3b252359..65b81d7e 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj @@ -564,6 +564,10 @@ + + + + @@ -704,6 +708,7 @@ + diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters index 8a4e4d8a..86bc0026 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters @@ -713,6 +713,9 @@ Source Files + + Source Files\Bullet Types + @@ -846,6 +849,9 @@ Configurations\GFX + + Documentation\Mechanics + diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index 5c2c7d87..6b06fe87 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -606,7 +606,7 @@ void AiL::UpdateBullets(float fElapsedTime){ const auto CollisionCheck=[&](){ if(b->friendly){ for(Monster&m:MONSTER_LIST){ - if(geom2d::overlaps(geom2d::circle(m.GetPos(),12*m.GetSizeMult()),geom2d::circle(b->pos,b->radius))){ + if(geom2d::overlaps(m.Hitbox(),geom2d::circle(b->pos,b->radius))){ if(b->hitList.find(&m)==b->hitList.end()&&m.Hurt(b->damage,b->OnUpperLevel(),0)){ if(!b->hitsMultiple){ if(b->MonsterHit(m)){ @@ -619,7 +619,7 @@ void AiL::UpdateBullets(float fElapsedTime){ } } } else { - if(geom2d::overlaps(geom2d::circle(player->GetPos(),12*player->GetSizeMult()/2),geom2d::circle(b->pos,b->radius))){ + if(geom2d::overlaps(player->Hitbox(),geom2d::circle(b->pos,b->radius))){ if(player->Hurt(b->damage,b->OnUpperLevel(),0)){ if(b->PlayerHit(player.get())){ b->dead=true; @@ -804,7 +804,6 @@ void AiL::RenderWorld(float fElapsedTime){ if(player->teleportAnimationTimer>0){ playerScale.x=120*float(abs(pow(player->teleportAnimationTimer-"Wizard.Right Click Ability.AnimationTime"_F/2,3))); pos=player->teleportStartPosition.lerp(player->teleportTarget,("Wizard.Right Click Ability.AnimationTime"_F-player->teleportAnimationTimer)/"Wizard.Right Click Ability.AnimationTime"_F); - std::cout<attackBuffs=player->GetStatBuffs({"Attack","Attack %"}); view.DrawPartialRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale*scale,attackBuffs.size()>0?Pixel{255,uint8_t(255*abs(sin(1.4*attackBuffs[0].duration))),uint8_t(255*abs(sin(1.4*attackBuffs[0].duration)))}:WHITE); diff --git a/Adventures in Lestoria/AdventuresInLestoria.h b/Adventures in Lestoria/AdventuresInLestoria.h index 38f1a994..f57be5bf 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.h +++ b/Adventures in Lestoria/AdventuresInLestoria.h @@ -56,6 +56,9 @@ All rights reserved. #include "olcPGEX_SplashScreen.h" #include "olcPixelGameEngine.h" +#define CreateBullet(type) BULLET_LIST.push_back(std::make_unique(type +#define EndBullet )); + class AiL : public olc::PixelGameEngine { friend class GameState; diff --git a/Adventures in Lestoria/BulletTypes.h b/Adventures in Lestoria/BulletTypes.h index 00a11075..ba0764af 100644 --- a/Adventures in Lestoria/BulletTypes.h +++ b/Adventures in Lestoria/BulletTypes.h @@ -78,4 +78,16 @@ struct ChargedArrow:public Bullet{ void Update(float fElapsedTime)override; bool PlayerHit(Player*player)override; bool MonsterHit(Monster&monster)override; +}; + +struct FrogTongue:public Bullet{ + vf2d targetPos; + float tongueLength; + float tongueSpd; + float duration; + FrogTongue(vf2d pos,vf2d targetPos,float lifetime,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); + void Update(float fElapsedTime)override; + bool PlayerHit(Player*player)override; + bool MonsterHit(Monster&monster)override; + void Draw()override; }; \ No newline at end of file diff --git a/Adventures in Lestoria/Chapter_1_Creatures_Part_2.txt b/Adventures in Lestoria/Chapter_1_Creatures_Part_2.txt new file mode 100644 index 00000000..098a10e9 --- /dev/null +++ b/Adventures in Lestoria/Chapter_1_Creatures_Part_2.txt @@ -0,0 +1,23 @@ +Wolf +HP: 110 +Atk: 12 +Move-Spd: 120% +Size: 90% +Strategie: Runs Towards Player, in 400 range it locks on current player position and charges that position with additional 15% move speed. +After Attack 1 second not doing anything. +After that move speed increases by 30% and it disengages on an 800 range where it circles around the player for 3 seconds. after that time move speed resets to normal and it attacks again. + +Bear +HP: 210 +Atk: 45 +Move-Spd: 60% +Size: 200% +Strategie: Moves towards player. Once next to the Player character builds itself up and smashes the ground infront of him. 2 seconds time to dodge attack once it locks the area its attacking. + +Frog +HP: 60 +Atk: 15 +Move-Spd: 70% +Size: 70% +Strategie: Moves with small jumps. if range to player is 350 or lower locks on to player and keep facing its direction without moving for the next 1.5 seconds it will attack the position the player was last in this time frame with another 1 seconds delay. Tongue max range: 450 +after attack 0.5 seconds doing nothing then starts again closing the gap or prepares for an attack again depending if range is bigger then 350 again. \ No newline at end of file diff --git a/Adventures in Lestoria/FrogTongue.cpp b/Adventures in Lestoria/FrogTongue.cpp new file mode 100644 index 00000000..f20515b6 --- /dev/null +++ b/Adventures in Lestoria/FrogTongue.cpp @@ -0,0 +1,86 @@ +#pragma region License +/* +License (OLC-3) +~~~~~~~~~~~~~~~ + +Copyright 2024 Joshua Sigona + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions or derivations of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions or derivative works in binary form must reproduce the above +copyright notice. This list of conditions and the following disclaimer must be +reproduced in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may +be used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +Portions of this software are copyright © 2023 The FreeType +Project (www.freetype.org). Please see LICENSE_FT.txt for more information. +All rights reserved. +*/ +#pragma endregion + +#include "BulletTypes.h" +#include "AdventuresInLestoria.h" +#include "util.h" + +INCLUDE_game +INCLUDE_MONSTER_LIST +INCLUDE_GFX + +FrogTongue::FrogTongue(vf2d pos,vf2d targetPos,float lifetime,int damage,bool upperLevel,bool friendly,Pixel col) + :Bullet(pos,{},0,damage,upperLevel,friendly,col),targetPos(targetPos),tongueSpd(tongueSpd),tongueLength(0.f){ + this->lifetime=lifetime; + duration=lifetime; +} +void FrogTongue::Update(float fElapsedTime){ + geom2d::linelineToTarget(pos,targetPos); + + tongueLength=util::lerp(0,lineToTarget.length(),sin((lifetime*PI)/duration)); + + if(!friendly&&geom2d::overlaps(game->GetPlayer()->Hitbox(),lineToTarget)){ + PlayerHit(game->GetPlayer()); + } + if(friendly){ + for(Monster&m:MONSTER_LIST){ + if(geom2d::overlaps(m.Hitbox(),lineToTarget)){ + MonsterHit(m); + } + } + } +} +bool FrogTongue::PlayerHit(Player*player){ + player->Hurt(damage,OnUpperLevel(),0); + deactivated=true; + return false; +} +bool FrogTongue::MonsterHit(Monster&monster){ + monster.Hurt(damage,OnUpperLevel(),0); + deactivated=true; + return false; +} +void FrogTongue::Draw(){ + geom2d::linelineToTarget(pos,targetPos); + vf2d drawVec=lineToTarget.vector().norm()*3; + + vf2d tongueEndPos=geom2d::line(pos+drawVec,targetPos).upoint(sin((lifetime*PI)/duration)); + + game->view.DrawRotatedDecal(pos+drawVec,GFX["tongue.png"].Decal(),drawVec.polar().y,{0.f,1.f},{tongueLength,1.0f},col); + game->view.DrawRotatedDecal(tongueEndPos,GFX["tongue_end.png"].Decal(),drawVec.polar().y,{2.f,2.f},{1.f,1.f},col); +} \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index 5b93d57e..a8b95e77 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -583,4 +583,8 @@ const EventName&Monster::GetDeathSound(){ } const EventName&Monster::GetWalkSound(){ return MONSTER_DATA[name].GetWalkSound(); +} + +geom2d::circleMonster::Hitbox(){ + return {GetPos(),12*GetSizeMult()}; } \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index d0b79a59..e5a29150 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -163,6 +163,7 @@ public: void SetZ(float z); const std::function&GetStrategy()const; void SetSize(float newSize,bool immediate=true); + geom2d::circleHitbox(); void SetStrategyDrawFunction(std::functionfunc); std::functionstrategyDraw=[](AiL*pge){}; const ItemAttributable&GetStats()const; diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index 6b8ee582..9c51a53a 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -537,6 +537,10 @@ void Player::Update(float fElapsedTime){ } } + if(game->GetKey(G).bPressed){ + CreateBullet(FrogTongue)(GetPos(),game->GetWorldMousePos(),1.f,10,OnUpperLevel(),true,RED)EndBullet; + } + #pragma region Warrior switch(GetState()){ case State::SWING_SWORD:{ @@ -1215,4 +1219,8 @@ const float Player::GetAttackRecoveryRateReduction()const{ void EntityStats::Reset(){ equipStats.clear(); baseStats.clear(); +} + +geom2d::circlePlayer::Hitbox(){ + return {GetPos(),12*GetSizeMult()/2}; } \ No newline at end of file diff --git a/Adventures in Lestoria/Player.h b/Adventures in Lestoria/Player.h index 0f322ec1..ba38e0f3 100644 --- a/Adventures in Lestoria/Player.h +++ b/Adventures in Lestoria/Player.h @@ -200,6 +200,8 @@ public: virtual std::string&GetIdleSAnimation()=0; virtual std::string&GetIdleWAnimation()=0; + geom2d::circleHitbox(); + void CheckEndZoneCollision(); CastInfo&GetCastInfo(); diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index f33202d1..ddcbae38 100644 --- a/Adventures in Lestoria/Version.h +++ b/Adventures in Lestoria/Version.h @@ -39,7 +39,7 @@ All rights reserved. #define VERSION_MAJOR 0 #define VERSION_MINOR 2 #define VERSION_PATCH 1 -#define VERSION_BUILD 5724 +#define VERSION_BUILD 5735 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/Wizard.cpp b/Adventures in Lestoria/Wizard.cpp index 6871e26b..7f1db84b 100644 --- a/Adventures in Lestoria/Wizard.cpp +++ b/Adventures in Lestoria/Wizard.cpp @@ -136,7 +136,6 @@ void Wizard::InitializeClassAbilities(){ p->teleportAnimationTimer="Wizard.Right Click Ability.AnimationTime"_F; p->teleportTarget=teleportPoint; p->teleportStartPosition=p->GetPos(); - std::cout<<"Start Position: "<<(p->teleportStartPosition)<iframe_time="Wizard.Right Click Ability.IframeTime"_F; for(int i=0;i<"Wizard.Right Click Ability.ParticleCount"_I;i++){ game->AddEffect(std::make_unique(p->GetPos()+vf2d{(util::random("Wizard.Right Click Ability.ParticleRange"_F/100*2)-"Wizard.Right Click Ability.ParticleRange"_F/100)*12,(util::random("Wizard.Right Click Ability.ParticleRange"_F/100*2)-"Wizard.Right Click Ability.ParticleRange"_F/100)*12},util::random("Wizard.Right Click Ability.ParticleLifetimeMax"_F)+"Wizard.Right Click Ability.ParticleLifetimeMin"_F,"circle.png",p->upperLevel,"Wizard.Right Click Ability.ParticleSize"_F,"Wizard.Right Click Ability.ParticleFadetime"_F,vf2d{util::random("Wizard.Right Click Ability.ParticleSpeedMax"_F*2)+"Wizard.Right Click Ability.ParticleSpeedMin"_F,util::random("Wizard.Right Click Ability.ParticleSpeedMax"_F*2)+"Wizard.Right Click Ability.ParticleSpeedMin"_F},"Wizard.Right Click Ability.ParticleColor"_Pixel)); @@ -153,7 +152,7 @@ void Wizard::InitializeClassAbilities(){ Wizard::ability1.action= [](Player*p,vf2d pos={}){ float angleToCursor=atan2(game->GetWorldMousePos().y-p->GetPos().y,game->GetWorldMousePos().x-p->GetPos().x); - BULLET_LIST.push_back(std::make_unique(FireBolt(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F},"Wizard.Ability 1.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 1.InitialDamageMult"_F),p->upperLevel,true,"Wizard.Ability 1.BulletColor"_Pixel))); + CreateBullet(FireBolt)(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F},"Wizard.Ability 1.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 1.InitialDamageMult"_F),p->upperLevel,true,"Wizard.Ability 1.BulletColor"_Pixel)EndBullet; SoundEffect::PlaySFX("Wizard Fire Bolt Shoot",SoundEffect::CENTERED); return true; }; @@ -162,7 +161,7 @@ void Wizard::InitializeClassAbilities(){ Wizard::ability2.action= [](Player*p,vf2d pos={}){ float angleToCursor=atan2(game->GetWorldMousePos().y-p->GetPos().y,game->GetWorldMousePos().x-p->GetPos().x); - BULLET_LIST.push_back(std::make_unique(LightningBolt(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F},"Wizard.Ability 2.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 2.DamageMult"_F),p->upperLevel,true,"Wizard.Ability 2.BulletColor"_Pixel))); + CreateBullet(LightningBolt)(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F},"Wizard.Ability 2.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 2.DamageMult"_F),p->upperLevel,true,"Wizard.Ability 2.BulletColor"_Pixel)EndBullet; SoundEffect::PlaySFX("Wizard Lightning Bolt Shoot",SoundEffect::CENTERED); return true; }; diff --git a/Adventures in Lestoria/assets/Campaigns/1_2.tmx b/Adventures in Lestoria/assets/Campaigns/1_2.tmx index a20a8771..65c336a8 100644 --- a/Adventures in Lestoria/assets/Campaigns/1_2.tmx +++ b/Adventures in Lestoria/assets/Campaigns/1_2.tmx @@ -1,5 +1,5 @@ - + @@ -1218,4 +1218,12 @@ + + + + + + + + diff --git a/Adventures in Lestoria/assets/Campaigns/1_5.tmx b/Adventures in Lestoria/assets/Campaigns/1_5.tmx index 935b803f..9fd07eaf 100644 --- a/Adventures in Lestoria/assets/Campaigns/1_5.tmx +++ b/Adventures in Lestoria/assets/Campaigns/1_5.tmx @@ -1,5 +1,5 @@ - + @@ -1109,4 +1109,12 @@ + + + + + + + + diff --git a/Adventures in Lestoria/assets/Campaigns/1_7.tmx b/Adventures in Lestoria/assets/Campaigns/1_7.tmx index 538b555f..56dd2031 100644 --- a/Adventures in Lestoria/assets/Campaigns/1_7.tmx +++ b/Adventures in Lestoria/assets/Campaigns/1_7.tmx @@ -1,5 +1,5 @@ - + @@ -685,4 +685,12 @@ + + + + + + + + diff --git a/Adventures in Lestoria/assets/config/Monsters.txt b/Adventures in Lestoria/assets/config/Monsters.txt index 8d541ea8..1d0e01e2 100644 --- a/Adventures in Lestoria/assets/config/Monsters.txt +++ b/Adventures in Lestoria/assets/config/Monsters.txt @@ -260,14 +260,16 @@ Monsters #Size of each animation frame SheetFrameSize = 24,24 + # Tongue shoots from (9,13) + # Frame Count, Frame Speed (s), Frame Cycling (Repeat,OneShot,PingPong,Reverse) - IdleAnimation = 3, 0.2, PingPong - JumpAnimation = 4, 0.06, PingPong - ShootAnimation = 10, 0.1, OneShot - DeathAnimation = 4, 0.1, OneShot + IdleAnimation = 4, 0.13, PingPong + JumpAnimation = 10, 0.06, Repeat + ShootAnimation = 6, 0.15, PingPong + DeathAnimation = 6, 0.1, OneShot # Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity - DROP[0] = Wolf Skin,35%,1,2 + DROP[0] = Frog Skin,35%,1,2 Hurt Sound = Monster Hurt Death Sound = Slime Dead diff --git a/Adventures in Lestoria/assets/config/gfx/gfx.txt b/Adventures in Lestoria/assets/config/gfx/gfx.txt index 4150236d..6e64e44e 100644 --- a/Adventures in Lestoria/assets/config/gfx/gfx.txt +++ b/Adventures in Lestoria/assets/config/gfx/gfx.txt @@ -47,6 +47,8 @@ Images GFX_Pixel = pixel.png GFX_Title = title_transparent.png GFX_TitleBack = title_back.png + GFX_FrogTongue = tongue.png + GFX_FrogTongueEnd = tongue_end.png # Ability Icons GFX_Warrior_BattleCry_Icon = Ability Icons/battlecry.png diff --git a/Adventures in Lestoria/assets/items/Frog Skin.png b/Adventures in Lestoria/assets/items/Frog Skin.png index 57d8e4dec82eb7dc33b106ab838a0444ae765556..7ef6e8dee0f6a8a0595ce5a384fa1b77e53a5174 100644 GIT binary patch literal 4435 zcmeHLdr%X19^d$&fKtUmMNf1uR!%9K?B=~|A_4)DYA`|+I8V>b?gqAyY?>@25b=j<>6R+-hqmvBHG?>6P|OOxnbt|ADL`+ z_xt$#zTeO9`~A)Dm$%Fr=@H@6!yyQYFdFok;2tAfp&{V6)uVd?Zg+i_96pouz)lxu zV+$FWFL5$3<7I6SHL~hKtV0n%|y% z6G9hrZeleRARm^eDA%F*4n{a^QV)0Cffv?YYX7Y^%?_9r1Tu-~n!-aj?>KshDr0*TKCM1% zf7FEb1Uk%jP{ClRjS`7h&97zPI()&U<;s{@L1I<6rNGLw_jM z%-77TGraY9PT9-YDsTO}-JN*7XBMo8UwoqOHe{-8v}ofPw?Fevv}gmiX~8DhiMRDW zZ0xsB>kc>$otk^)L09jIr!DmtN=xs58qojWlGf7=CtvuJtyz(%Q0Ln`LZFg{pprlnV&BUncXnvP&0h$&3lPY zbSwO*^7}bu)WXN*9lw8D^yihkb3K(^R0F#cI+NG>8rSQOSp8_rL*I(>8KSW_{(7Nk zMb@Lz%dxAUTzb!RecQT|8eKL2{k!Wn0(gYb1_-I3=8Eepm188xFB5XB>;e(;YrwQFLbyG zuTC7mCBU;FmWttk316TS=a|fJ3g=>Ag+w7ikyI~REE6vXhqW%+Mr7*K`XIoYPMpv4 zPC_d6csvr1T*A5Xq!^ClQdB0D$q-jJGGCJHm>7W<^3V=&kfP>O9%4%a&2&S?!h=P{W2&u3t z5jBHrXq(k4M>X;x5KCMvs7kVMXjB3e4WLLBqo&j{6tUXm6r#XwN`$0H1%hg9Hl;#F zS~aLG07X+o8t1Z;U^-blna4<-j=X?EAe=}t8+Brt1Ra!^3rXGv96%4S4x0102UQl< z&SdeVz$d0gm8e<`a#!OTwL&pyw48CdK`jcX7%GtmHNwOYARRz0DO4%|2zY>Oh!ht? z@|?@UafLdu5ELwU4ya9_p=gpP^(4;#P*kQMP>jH277Qg6;HyDU6@d=1=V;be^1rNw z_JOs*NjI==Fn&ou6zr)i=8fQ6@U@T)G!qO5+JYddUuCVp&DnSl>0*-d0FQtx zP@n;>;JJNMHE(z{o_t1_2S6EuV#pw6xLDektW;Pr`m@za|BDZ8z+lJ{1N?$Auylcy zP&%*__VEQ`{W(8<^YC*H0mGw%jELV+x<=_55d$MK9<8oXx<S<8>;Fbq_~2oR zaey6=2OO1pDbY9Jm=$7Ol&*)`pi7YNZ0_pkKoaIOf+{M7KM!8uji%HW z?uaH%o{KGQK6n6#rtzscd;$)f|Ji9$gBZnE3(ztK@N|r$y&3zrXE=!yyGt@y%ndOeXc$7EMXF rY}|U__Jl{#S03J-b-4QbNe{=B{lT1eA)zK2L=PELGxSH2SFigIrs`9K delta 655 zcmV;A0&xA)BI5;+BYy#eX+uL$Nkc;*aB^>EX>4Tx04R}tkv&MmKpe$iQ?()$5eteq zWT;Mdu_BJ8ibb$c+6t{Ym|XfHG-*guTpR`0f`cE6RR+H=vN9R1AHQJjOm6&yiPo`Y3ZEzi9@U`Da7Z*lLlRo_>t?f%Ws@Z4huXp zWMtEG#35p_)WLEGv$CNQPZNg~Rik_%=d!|ii?dp(vDQ8L3xfr1Im30DV@O~TNu(e` zMhzRNz(RypjeisqY1)r__(vUoid-_eHo(X+k1AA1jvxFFes^mYqf>5DFb;IT*!IUb z5ZDEpb=&?vw(aH#;C}|Lw6?$60A@c)ueY`E5iqa~TwJ#`c@MbU0Y;v5$&eh$Pg5us zf%h}|raUlo3-qnJy|wmn`T%69tEC&@;1HN7QTCe0yCZu$d;9lHtG^%1oN~&hSdO1g=WN@lVlJmf4fOUK~zY`?US(zgD@0D zk3oo?T-;5Df}fJn&v5ipI`%Uh`zeBhc5~>`sR)9dgbEs^BD9dZ1l~>F<=uA!4h}yL zZ_A}n%3+CncAd9NAe3^5^JtO3UmjKwv9-A}nnaxdNYn{sGyriPStk^0&2`bEj3y}C z2_KX#%4l@aQCmgCnniW$<7L&nzgb0{`quYi%aVud8Gy2RP0q2tOF$Pr&8@cbU#IAE p4|7(Gbz71SkLlqb&B5Uh@CGY#N6}@~(`oJ&)(m?=1H`-vs7BB zwh#h=C|O&P9l+->czey41Ao`*Pd@@5H^ZD<`3_V8j2p;dF#Q1-Ka>l=fDk4F0ttD} zvfr+J3pRhMTXm!K{E`s$iqcy4mCi-7C&~xc-Hi6hoUcCAy5LY8S-P+w&E}!+VfZ@d zwMi+@*Jh7C4Tz(Qn&XXQ=!nt>o__Oj;!%TPr-KOS+K9L-^=3zgTQ$$TO>83{9HKhi zOh{Ewm20Vvz1|Zxbu;+2qDTgj@Q~Kt+frJF`lOu4M`L!5B|^WIDJelIo4SJp;NMQbZrCncYGy^}labG7&Nn}Yo4eAxUiQTUDc-^#5L z$JWIz$9RTR9vT@WR0=d$8pS5}TKA;d2V4NTg?}GfsF9yhhTn5cZO_ZpYIH`c%VYOEgCzX%!=_7@ zsy5qiku^MB+?V5O^(gc~&89cv)VB`zoaNTmNMC4#RS1VSjhpPsM;p9f7fUl8OYRIm zAeHU$r<)*Sh*rbQg#7LEdMjr#U&-rJ>S)-mWOXT-wi|bXb~%2Es$1qCtBDZ8i5YqL|SMZLIeR=0G|pAVfnLp#1Iqs3@#CTmxxht z*o+E)j|tqBVh=Ot1OhM|5{E=1EJB#USU71R%s7zFAUcq@e1!m4CU9RqpG!oc1Ofq4 zppWDP`k*ib0s)1_qOe#5sDa>xviZ~y1e>>30x^q026(hUCYR6TuwfERsy8QyZvuyd z`>=2EvA7hCkB?z#m|Nsyxsu<_DLS))f2i77_}4m@Mv$6-f3EntUeX zAF_UkO|mnS&fGvi^Y6GnXn)InMj6ziP>5s>El3icHQ59%@lT|4XiPeB=9OV+LD#9u`Q4Zb|uw+Zd4K|ItWF;&>4pM01iRN6R-#z z+M9r&(!J3LhBuu?#TfzKIGW)M6rD!g!U<$i!E!QLR38AvW&6x*NC+oxwzoEcW0B~8 zB=-JPJ_9rW&j6E6=LmTJY&kJmfFqwO;S*zoHb5I05sYv~1~?36*3}H+4j_;R_M!w8 zgGTD(X3QmpA%f|E)KVp#3Ifc?!EA`;fdG}y33TFc{7v8z2$;n3`!)qUP;@GvN~ZDw z5EPBY5z!bT2J3`D6LE$_3=V-dB%uFx z#ONC!a0W(Hgg2T_MKEakIDH1g2tZS3v-*zC<1qLFY9O%L2jmgt3T)6Bu3#I!mg=`3 zqY?N5k~)BtA%T_V!hb%d05MhEaZEn6c;0v zulB>$A&~h!*5u7jAmg!n3W_e{=Ukg6k*pEJW<9C}_Uiy<+c&A{8tmhehnPT#^&sc&Vw zaf32X^=ho+l`pk6&F*DOqvm}XD1Qr<(6(xxXkyT?7gH?4nB>TynB+lkXHV^q0h8n6 z(W4jBtDc0Ox2axGX)cbH-*&Qlhn4iboTE?u!$Ko6ja%gH z4ltl2D`nqhwzR05I<}8hhCh!Oa)uR)(x1O8qMS1eI#8F7XA9o9^*|@)9XZrync$Xr zw(7FWib%h@5>wwkXwZbDwYn>I5ec8KaWCa``ks2I^Q_!y&4Bb0UQAQ}Q8Jsop=Mz4 z#p#;$i6x^E50jbW&?3{A-pSyJ#*%}pvq~n+pvw}76=yjPmwLT3RMp8@!R-z154TO; z%55q2=@@?*zNRC*v2l!mQiLX?N58s1a^~XD-h-xT`r`$x{&Cwug;RdE7}3Nu_tDFg z$L~riatI$^sgHJ*7C(L$-Kuq4Z<~30u%~-}wEIA?fheLP{8-7sWm&Zm*(EiqyQe(! zRE!DtklHGLjO}K4#fF&Gi4N~tF%l5!-)@?8k+Tt{zUpIT%yJlFYH`i>c1gf)2XwTm zpJ2BLJlr*>bfCoIiX7&0u5h}NJJoTnL_f6#z5YZ{qfyz?-9p9Mvnw59)J&jVIm~za(Agy{zN!tPoQw+@5{-gL@E*cE<-% z)p!X7xbw?SjC$-|%56Du$T|W#j#RSnu&ryhn_RsaGn(u2$9b2xYrA;vtO(Yvo30<+ z(oy{F`@EEjO#f)#tKQc;c6W7NtLXmnK7m1ruC703VNB_l3ucS=uxPjQ#%z=@H3_QX zvvGGvF4G>!igVC<1!`Htu_@>`gT*^-6o@(R{ES2Qj(B>d2P=B8BA_pI8a#GosRZV= zq@AouDk;UhQK@QNCi0L;jC|sg?NJgDR3b8HuwFRzd(C;ZtW`q_2bVx`q2ZR{|9AdD zARBVtm(@lI{}RJUyGY44s$)&Kj~XV8f~G^KuB3@gvyNSoH|)N5fzz#n{j^_rodit? zYLz!weoGdCpt|9-z>YSl&3YCetwbK;TzdijpVwl;wvY^HdkL4!&VR7 z-*Udtf>B}fUSZ(=V-4NBm7_+MnmM$5-+lF)O0$X(gY}ya#dTt8+ct*DtBx9N)zlii zK^9gumd0wN)OmCli&iQZ3rQ(wISQ{Xi1U}io!Si^d1q=~kz3I3x0hLTgRav5cT-I7 zER$dwxi^H=Wd+Pp-5r#$B^Q7yhj>Msg&k84R zy)*0HC)^of-)W(z;`t)JH6sb>R~t|V?ctP?&YhRJ1yvKPmBmRdu=E`_Gp|k}H3FRx z+1a}GvPUue9Cf+qPQ% zE$uJ6_=L}&_vcb8-P9#h)mh}YZ4r4oLb$wRoCPJGeUMehZAKgxM8%WFvcbxrL%bH3y0jz3;eZZC#6RArRx*_&h1r!SLSJLerw*0n^8>}8l$sO6tb^wM3dCN536TZ4Fl9+HmmF@G21iq0Q{#mpKjZaf>yYBY z8~fz6&_}BFbXagMkKzlX#(UCbZyp~lh!9TqAG@6A;AgAa zh>4fu`Ye8PiQt9dt0O(fU*?u2DqDIAYbY(<%8`L#7HJKd1)Eakt zG3V`^^~F+-5^eimm$bPB9$zqPRl{My%F-QIRM%}YDt+t2qGWufwO;XE%A-eJJbdD- zssepvH26wI&&0hqFF&4l&+?a@n!tm*?<>mkzqifa`eyY)eYTk~{JN8Wmaf15f^o*b zBA@LarM~VryvuO1o#{XTg9I0YGh+f5_YTX(Rhf9<$y{ntks6 z^;fo69u%LuZOi-gX&oo7?~X34dOndY{d9Tf`MdjU#CH{}EUvSOPSM`(RpxZy(Hij< zi_fkCS>MkIH}3sDZ;qTS`+5EO&ps{vdhon;ht>1+)aAWv7Tx4zJXbTZDna~s;NpiL zK2*z;&#V?GJ!d{Kwo>%VjPZOIhW7JR2*&%zBK3%3jNY=1uG{EIXG zZ?JI#9BClZ(r>{dI0><<$n0fGRl8XXosTITh%Yc=_z9SLU<+J$dN<OV@LD-FEElL5Gn z>vEPa(!T%mJ{VTW?fVox=j)$uf6oYoJH5ZTZnEt4+uuYc-Z}lH_VedWzSq~U{x)~{ zg7f_KM)BP7l3J(wfRXh2#63&S)y0u}j;ET&ElHPi+PCh_p~(eTo%k)|-M6KFn*RUf z*9##f4YjH3datjzZ?Y`^^@>`%`bjLn=+EX>4Tx04R}tkv&MmP!xqvQ>CI61v7{^WT;MdQ4vR}Vi7EqwnD28CNKSiCJjl7 zi=*ILaPVib>fqw6tAnc`2>yULJ2)x2NQw6)g%&Yhc)XAE?m4`7A0RX;OtZS?08O{e zR6HhSbE{J56#;}Xh(3rivy4P~F$3TFx~FccyC~1{@B6d*)x5=kfJ8jU4AUmwAfDN@ z4bJ<-A}h%%@j3B?Nf#u3KlnXct1va`C52-^_lx6vi~*rtpjmgE?_!PL200009a7bBm000XU000XU0RWnu7ytkO2XskI zMF;2s3=lXQz^QW}0000GNkl0@?kEP)EX>4Tx04R}tkv&MmP!xqvQ>CI61v7{^WT;MdQ4vR}Vi7EqwnD28CNKSiCJjl7 zi=*ILaPVib>fqw6tAnc`2>yULJ2)x2NQw6)g%&Yhc)XAE?m4`7A0RX;OtZS?08O{e zR6HhSbE{J56#;}Xh(3rivy4P~F$3TFx~FccyC~1{@B6d*)x5=kfJ8jU4AUmwAfDN@ z4bJ<-A}h%%@j3B?Nf#u3KlnXct1va`C52-^_lx6vi~*rtpjmgE?_!PL200009a7bBm000XU000XU0RWnu7ytkO2XskI zMF;2s3=lXVaiW?80000rNkl3fw6hf!~GIMdL oDyc<8U>kth;@ttoperator*(T(1.0 - t)) + (v1 * T(t)); - } - - inline constexpr v_2d operator * (const T& rhs) const - { - return v_2d(this->x * rhs, this->y * rhs); + return (*this) * (T(1.0 - t)) + (v1 * T(t)); } // Compare if this vector is numerically equal to another