diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj b/Adventures in Lestoria/Adventures in Lestoria.vcxproj index e3ed1d0b..902ded40 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj @@ -381,7 +381,15 @@ - + + + + + + + + + @@ -740,7 +748,11 @@ - + + + + + diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters index a6471bda..12302572 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters @@ -138,7 +138,7 @@ Header Files - + Header Files @@ -663,6 +663,12 @@ Header Files + + Header Files + + + Header Files + @@ -686,7 +692,7 @@ Source Files - + Source Files @@ -1130,6 +1136,9 @@ Source Files\Bullet Types + + Source Files\Bullet Types + diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index 0d9158ff..5a140108 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -97,7 +97,7 @@ safemapANIMATION_DATA; std::vector>MONSTER_LIST; std::unordered_mapSPAWNER_LIST; std::vector>DAMAGENUMBER_LIST; -std::vector>BULLET_LIST; +std::vector>BULLET_LIST; std::optional>SPAWNER_CONTROLLER; safemapGFX; utils::datafile DATA; @@ -722,13 +722,13 @@ void AiL::UpdateEffects(float fElapsedTime){ } void AiL::UpdateBullets(float fElapsedTime){ for(auto it=BULLET_LIST.begin();it!=BULLET_LIST.end();++it){ - Bullet*b=(*it).get(); + IBullet*b=(*it).get(); b->_Update(fElapsedTime); } - std::erase_if(BULLET_LIST,[](std::unique_ptr&b){return b->IsDead();}); + std::erase_if(BULLET_LIST,[](std::unique_ptr&b){return b->IsDead();}); } -const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float z,const HurtType hurtTargets)const{ +const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags)const{ HurtList hitList; const bool CheckForMonsterCollisions=hurtTargets&HurtType::MONSTER; const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER; @@ -737,7 +737,7 @@ const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float for(std::unique_ptr&m:MONSTER_LIST){ const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult())); if(InRange){ - HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z); + HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags); hitList.push_back({&*m,returnVal}); } } @@ -745,21 +745,21 @@ const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float if(CheckForPlayerCollisions){ const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult())); if(InRange){ - HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z); + HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags); hitList.push_back({GetPlayer(),returnVal}); } } return hitList; } -const HurtList AiL::HurtMonsterType(vf2d pos,float radius,int damage,bool upperLevel,float z,const std::string_view monsterName)const{ +const HurtList AiL::HurtMonsterType(vf2d pos,float radius,int damage,bool upperLevel,float z,const std::string_view monsterName,HurtFlag::HurtFlag hurtFlags)const{ if(!MONSTER_DATA.count(std::string(monsterName)))ERR(std::format("WARNING! Cannot check for monster type name {}! Does not exist!",monsterName)); HurtList hitList; for(std::unique_ptr&m:MONSTER_LIST){ - const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult())); + const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),m->GetCollisionRadius())); if(m->GetName()==monsterName&&InRange){ - HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z); + HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags); hitList.push_back({&*m,returnVal}); } } @@ -767,40 +767,39 @@ const HurtList AiL::HurtMonsterType(vf2d pos,float radius,int damage,bool upperL return hitList; } -void AiL::ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtType knockbackTargets)const{ - const bool CheckForMonsterCollisions=knockbackTargets&HurtType::MONSTER; - const bool CheckForPlayerCollisions=knockbackTargets&HurtType::PLAYER; +const HurtList AiL::HurtConeNotHit(vf2d pos,float radius,float angle,float sweepAngle,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags)const{ + HurtList affectedList; + const bool CheckForMonsterCollisions=hurtTargets&HurtType::MONSTER; + const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER; + if(CheckForMonsterCollisions){ for(std::unique_ptr&m:MONSTER_LIST){ - if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ - m->ProximityKnockback(pos,knockbackAmt); + if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ + float angleToMonster=geom2d::line{pos,m->GetPos()}.vector().polar().y; + float angleDiff=util::angle_difference(angleToMonster,angle); + if(abs(angleDiff)<=sweepAngle){ + HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags); + affectedList.push_back({&*m,returnVal}); + hitList.insert(&*m); + } } } } if(CheckForPlayerCollisions){ - if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){ - GetPlayer()->ProximityKnockback(pos,knockbackAmt); + if(!hitList.count(GetPlayer())&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){ + float angleToPlayer=geom2d::line{pos,GetPlayer()->GetPos()}.vector().polar().y; + float angleDiff=util::angle_difference(angleToPlayer,angle); + if(abs(angleDiff)<=sweepAngle){ + HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags); + affectedList.push_back({GetPlayer(),returnVal}); + hitList.insert(GetPlayer()); + } } } + return affectedList; } -void AiL::ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtList&knockbackTargets,const KnockbackCondition condition)const{ - using enum KnockbackCondition; - for(auto&[target,wasHurt]:knockbackTargets){ - if(condition==KNOCKBACK_HURT_TARGETS&&!wasHurt|| - condition==KNOCKBACK_UNHURT_TARGETS&&wasHurt)continue; - if(std::holds_alternative(target)){ - Player*player{std::get(target)}; - player->ProximityKnockback(pos,knockbackAmt); - }else - if(std::holds_alternative(target)){ - Monster*monster{std::get(target)}; - monster->ProximityKnockback(pos,knockbackAmt); - }else ERR("WARNING! Target list was holding an unknown type??? THIS SHOULD NOT BE HAPPENING!") - } -} - -const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets)const{ +const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags)const{ HurtList affectedList; const bool CheckForMonsterCollisions=hurtTargets&HurtType::MONSTER; const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER; @@ -808,7 +807,7 @@ const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList, if(CheckForMonsterCollisions){ for(std::unique_ptr&m:MONSTER_LIST){ if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ - HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z); + HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags); affectedList.push_back({&*m,returnVal}); hitList.insert(&*m); } @@ -816,7 +815,7 @@ const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList, } if(CheckForPlayerCollisions){ if(!hitList.count(GetPlayer())&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){ - HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z); + HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags); affectedList.push_back({GetPlayer(),returnVal}); hitList.insert(GetPlayer()); } @@ -824,36 +823,37 @@ const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList, return affectedList; } -const HurtList AiL::HurtConeNotHit(vf2d pos,float radius,float angle,float sweepAngle,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets)const{ - HurtList affectedList; - const bool CheckForMonsterCollisions=hurtTargets&HurtType::MONSTER; - const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER; - +void AiL::ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtType knockbackTargets)const{ + const bool CheckForMonsterCollisions=knockbackTargets&HurtType::MONSTER; + const bool CheckForPlayerCollisions=knockbackTargets&HurtType::PLAYER; if(CheckForMonsterCollisions){ for(std::unique_ptr&m:MONSTER_LIST){ - if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ - float angleToMonster=geom2d::line{pos,m->GetPos()}.vector().polar().y; - float angleDiff=util::angle_difference(angleToMonster,angle); - if(abs(angleDiff)<=sweepAngle){ - HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z); - affectedList.push_back({&*m,returnVal}); - hitList.insert(&*m); - } + if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ + m->ProximityKnockback(pos,knockbackAmt); } } } if(CheckForPlayerCollisions){ - if(!hitList.count(GetPlayer())&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){ - float angleToPlayer=geom2d::line{pos,GetPlayer()->GetPos()}.vector().polar().y; - float angleDiff=util::angle_difference(angleToPlayer,angle); - if(abs(angleDiff)<=sweepAngle){ - HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z); - affectedList.push_back({GetPlayer(),returnVal}); - hitList.insert(GetPlayer()); - } + if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){ + GetPlayer()->ProximityKnockback(pos,knockbackAmt); } } - return affectedList; +} + +void AiL::ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtList&knockbackTargets,const KnockbackCondition condition)const{ + using enum KnockbackCondition; + for(auto&[target,wasHurt]:knockbackTargets){ + if(condition==KNOCKBACK_HURT_TARGETS&&!wasHurt|| + condition==KNOCKBACK_UNHURT_TARGETS&&wasHurt)continue; + if(std::holds_alternative(target)){ + Player*player{std::get(target)}; + player->ProximityKnockback(pos,knockbackAmt); + }else + if(std::holds_alternative(target)){ + Monster*monster{std::get(target)}; + monster->ProximityKnockback(pos,knockbackAmt); + }else ERR("WARNING! Target list was holding an unknown type??? THIS SHOULD NOT BE HAPPENING!") + } } void AiL::PopulateRenderLists(){ @@ -882,7 +882,7 @@ void AiL::PopulateRenderLists(){ std::sort(MONSTER_LIST.begin(),MONSTER_LIST.end(),[](std::unique_ptr&m1,std::unique_ptr&m2){return m1->GetPos().yGetPos().y;}); std::sort(ItemDrop::drops.begin(),ItemDrop::drops.end(),[](ItemDrop&id1,ItemDrop&id2){return id1.GetPos().y&b1,std::unique_ptr&b2){return b1->pos.ypos.y;}); + std::sort(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr&b1,std::unique_ptr&b2){return b1->pos.ypos.y;}); std::sort(foregroundEffects.begin(),foregroundEffects.end(),[](std::unique_ptr&e1,std::unique_ptr&e2){return e1->pos.ypos.y;}); std::sort(backgroundEffects.begin(),backgroundEffects.end(),[](std::unique_ptr&e1,std::unique_ptr&e2){return e1->pos.ypos.y;}); @@ -919,7 +919,7 @@ void AiL::PopulateRenderLists(){ } } for(auto it=BULLET_LIST.begin();it!=BULLET_LIST.end();++it){ - Bullet*b=(*it).get(); + IBullet*b=(*it).get(); if(b->OnUpperLevel()){ bulletsUpper.push_back(b); }else{ @@ -1487,7 +1487,7 @@ void AiL::RenderWorld(float fElapsedTime){ ItemDrop::drops[dropInd].Draw(); ++dropsAfterLowerIt; } - for(const Bullet*const b:bulletsLower){ + for(const IBullet*const b:bulletsLower){ b->_Draw(); } for(const Effect*const e:foregroundEffectsLower){ @@ -1788,7 +1788,7 @@ void AiL::RenderWorld(float fElapsedTime){ ItemDrop::drops[dropInd].Draw(); ++dropsAfterUpperIt; } - for(const Bullet*const b:bulletsUpper){ + for(const IBullet*const b:bulletsUpper){ b->_Draw(); } for(const Effect*const e:foregroundEffectsUpper){ @@ -1965,54 +1965,7 @@ void AiL::RenderHud(){ for(std::vector>::iterator it=DAMAGENUMBER_LIST.begin();it!=DAMAGENUMBER_LIST.end();++it){ DamageNumber*dn=(*it).get(); - - #define NumberScalesWithDamage true - #define NormalNumber false - - auto DrawDamageNumber=[&](const bool ScaleWithNumber,std::string_view text,std::paircolorsEnemy,std::paircolorsFriendly,vf2d scaling={1.f,1.f}){ - vf2d textSize=GetTextSizeProp(text)*scaling; - if(!dn->friendly){ - vf2d additionalScaling={1.f,1.f}; - if(ScaleWithNumber)additionalScaling=dn->size; - - textSize*=additionalScaling; - vf2d drawPos=dn->pos-textSize/2.f; - - drawPos.x=std::clamp(drawPos.x,view.GetWorldTL().x,view.GetWorldBR().x-textSize.x); - drawPos.y=std::clamp(drawPos.y,view.GetWorldTL().y,view.GetWorldBR().y-textSize.y); - - view.DrawShadowStringPropDecal(drawPos,text,colorsEnemy.first,colorsEnemy.second,scaling*additionalScaling); - }else{ - vf2d drawPos=dn->pos-textSize/2.f; - drawPos.x=std::clamp(drawPos.x,view.GetWorldTL().x,view.GetWorldBR().x-textSize.x); - drawPos.y=std::clamp(drawPos.y,view.GetWorldTL().y,view.GetWorldBR().y-textSize.y); - - view.DrawShadowStringPropDecal(drawPos,text,colorsFriendly.first,colorsFriendly.second,scaling); - } - }; - - switch(dn->type){ - case HEALTH_LOSS:{ - std::string text=std::to_string(dn->damage); - DrawDamageNumber(NumberScalesWithDamage,text,{DARK_RED,{255,160,160}},{RED,VERY_DARK_GREY}); - }break; - case HEALTH_GAIN:{ - std::string text="+"+std::to_string(dn->damage); - DrawDamageNumber(NormalNumber,text,{DARK_GREEN,BLACK},{GREEN,VERY_DARK_GREY}); - }break; - case MANA_GAIN:{ - std::string text="+"+std::to_string(dn->damage); - DrawDamageNumber(NormalNumber,text,{BLUE,VERY_DARK_GREY},{BLUE,VERY_DARK_GREY}); - }break; - case INTERRUPT:{ - std::string text="Interrupted!"; - DrawDamageNumber(NormalNumber,text,{BLACK,VERY_DARK_GREY},{BLACK,VERY_DARK_GREY},{0.5f,1}); - }break; - case CRIT:{ - std::string text=std::to_string(dn->damage); - DrawDamageNumber(NumberScalesWithDamage,text,{YELLOW,DARK_YELLOW},{BLACK,{0,0,0,0}}); - }break; - } + dn->Draw(); } #ifdef _DEBUG diff --git a/Adventures in Lestoria/AdventuresInLestoria.h b/Adventures in Lestoria/AdventuresInLestoria.h index 5940c6f4..d31053c1 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.h +++ b/Adventures in Lestoria/AdventuresInLestoria.h @@ -171,7 +171,7 @@ private: void InitializeClasses(); int DEBUG_PATHFINDING=0; std::vectormonstersBeforeLower,monstersAfterLower,monstersBeforeUpper,monstersAfterUpper; - std::vectorbulletsLower,bulletsUpper; + std::vectorbulletsLower,bulletsUpper; std::vectorbackgroundEffectsLower,backgroundEffectsUpper,foregroundEffectsLower,foregroundEffectsUpper; float reflectionUpdateTimer=0; float reflectionStepTime=0; @@ -254,15 +254,16 @@ public: void AddEffect(std::unique_ptrforeground,std::unique_ptrbackground); //If back is true, places the effect in the background void AddEffect(std::unique_ptrforeground,bool back=false); - const HurtList Hurt(vf2d pos,float radius,int damage,bool upperLevel,float z,const HurtType hurtTargets)const; - void ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtType knockbackTargets)const; - void ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtList&knockbackTargets,const KnockbackCondition condition=KnockbackCondition::KNOCKBACK_HURT_TARGETS)const; + const HurtList Hurt(vf2d pos,float radius,int damage,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const; + const HurtList HurtMonsterType(vf2d pos,float radius,int damage,bool upperLevel,float z,const std::string_view monsterName,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const; //NOTE: This function will also add any enemies that were hit into the hit list! - const HurtList HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets)const; + const HurtList HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const; // angle: The central angle where the arc will extend from. // sweepAngle: The amount of radians to extend in both directions from the central angle. // NOTE: This function will also add any enemies that were hit into the hit list! - const HurtList HurtConeNotHit(vf2d pos,float radius,float angle,float sweepAngle,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets)const; + const HurtList HurtConeNotHit(vf2d pos,float radius,float angle,float sweepAngle,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const; + void ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtType knockbackTargets)const; + void ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtList&knockbackTargets,const KnockbackCondition condition=KnockbackCondition::KNOCKBACK_HURT_TARGETS)const; vf2d GetWorldMousePos(); bool LeftHeld(); bool RightHeld(); @@ -374,7 +375,6 @@ public: Overlay&GetOverlay(); void SetWindSpeed(vf2d newWindSpd); const vf2d&GetWindSpeed()const; - const HurtList HurtMonsterType(vf2d pos,float radius,int damage,bool upperLevel,float z,const std::string_view monsterName)const; void InitializeGameConfigurations(); void InitializePlayer(); void SetWorldZoom(float newZoomScale); diff --git a/Adventures in Lestoria/Arrow.cpp b/Adventures in Lestoria/Arrow.cpp index c7712181..592c3491 100644 --- a/Adventures in Lestoria/Arrow.cpp +++ b/Adventures in Lestoria/Arrow.cpp @@ -102,4 +102,8 @@ BulletDestroyState Arrow::MonsterHit(Monster& monster) fadeOutTime=0.2f; game->AddEffect(std::make_unique(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),0.25)); return BulletDestroyState::KEEP_ALIVE; +} + +void Arrow::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){ + if(friendly)hurtFlags|=HurtFlag::PLAYER_ABILITY; } \ No newline at end of file diff --git a/Adventures in Lestoria/Bomb.cpp b/Adventures in Lestoria/Bomb.cpp index 45087fe2..651b9782 100644 --- a/Adventures in Lestoria/Bomb.cpp +++ b/Adventures in Lestoria/Bomb.cpp @@ -104,4 +104,6 @@ BulletDestroyState Bomb::MonsterHit(Monster&monster){ void Bomb::Draw(const Pixel blendCol)const{ Bullet::Draw(blendCol); game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},bomb_animation.GetFrame(animation).GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,bomb_animation.GetFrame(animation).GetSourceRect().size/2,bomb_animation.GetFrame(animation).GetSourceRect().pos,bomb_animation.GetFrame(animation).GetSourceRect().size,scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,uint8_t(util::lerp(col.a,0,1-((fadeOutTime-GetFadeoutTimer())/fadeOutTime)))}); -} \ No newline at end of file +} + +void Bomb::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/Buff.h b/Adventures in Lestoria/Buff.h index c70bbbdf..8ab1e7aa 100644 --- a/Adventures in Lestoria/Buff.h +++ b/Adventures in Lestoria/Buff.h @@ -53,6 +53,7 @@ enum BuffType{ COLLISION_KNOCKBACK_STRENGTH, //Causes an amount of knockback based on intensity when hit via collision with this buff SELF_INFLICTED_SLOWDOWN, //Used for monsters and can't be applied by any player abilities. ADRENALINE_RUSH, + TRAPPER_MARK, }; class AiL; diff --git a/Adventures in Lestoria/Bullet.cpp b/Adventures in Lestoria/Bullet.cpp index 05bef1a5..a6ff3f1b 100644 --- a/Adventures in Lestoria/Bullet.cpp +++ b/Adventures in Lestoria/Bullet.cpp @@ -30,244 +30,17 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Portions of this software are copyright © 2024 The FreeType +Portions of this software are copyright © 2024 The FreeType Project (www.freetype.org). Please see LICENSE_FT.txt for more information. All rights reserved. */ #pragma endregion -#include "Bullet.h" -#include "AdventuresInLestoria.h" -#include "DEFINES.h" -#include "safemap.h" -#include "util.h" -#include "SoundEffect.h" -INCLUDE_ANIMATION_DATA -INCLUDE_game -INCLUDE_GFX -INCLUDE_MONSTER_LIST -INCLUDE_WINDOW_SIZE +#include "Bullet.h" Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale,float image_angle) - :pos(pos),vel(vel),radius(radius),damage(damage),col(col),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle){}; - +:IBullet(pos,vel,radius,damage,upperLevel,friendly,col,scale,image_angle){} +//Initializes a bullet with an animation. Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound) - :pos(pos),vel(vel),radius(radius),damage(damage),col(col),animated(true),rotates(rotatesWithAngle),lifetime(lifetime),hitsMultiple(hitsMultiple),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle),hitSound(std::string(hitSound)){ - this->animation.AddState(animation,ANIMATION_DATA.at(animation)); - this->animation.ChangeState(internal_animState,animation); -}; - -Animate2D::Frame Bullet::GetFrame()const{ - return animation.GetFrame(internal_animState); -} -void Bullet::UpdateFadeTime(float fElapsedTime){ - aliveTime+=fElapsedTime; - if(fadeInTime>0){ - if(fadeInTimer0){ - if(fadeOutTimer==0){ - lifetime=fadeOutTime; - } - fadeOutTimer+=fElapsedTime; - } -} - -void Bullet::Update(float fElapsedTime){} - -void Bullet::SimulateUpdate(const float fElapsedTime){ - simulated=true; - _Update(fElapsedTime); - simulated=false; -} - -void Bullet::_Update(const float fElapsedTime){ - if(dead)return; - UpdateFadeTime(fElapsedTime); - Update(fElapsedTime); - animation.UpdateState(internal_animState,fElapsedTime); - const bool CollisionCheckRequired=IsActivated()&&fadeInTimer==fadeInTime&&radius!=0.f; - if(CollisionCheckRequired){ - float totalDistance=(vel*fElapsedTime).mag(); - int iterations=int(std::max(1.f,(vel*fElapsedTime).mag())); - int totalIterations=iterations; - vf2d finalBulletPos=pos+vel*fElapsedTime; - if(IsPlayerAutoAttackProjectile()){finalBulletPos+=(game->GetWindSpeed()*game->GetElapsedTime())/float(totalIterations);} - distanceTraveled+=totalDistance/24.f*100.f; - const auto CollisionCheck=[&](){ - if(simulated)return true; - if(friendly){ - for(std::unique_ptr&m:MONSTER_LIST){ - if(geom2d::overlaps(m->BulletCollisionHitbox(),geom2d::circle(pos,radius))){ - if(hitList.find(&*m)==hitList.end()&&m->Hurt(damage,OnUpperLevel(),z)){ - if(!hitsMultiple){ - if(_MonsterHit(*m)==BulletDestroyState::DESTROY){ - dead=true; - } - return false; - }else _MonsterHit(*m); - hitList.insert(&*m); - } - } - } - } else { - if(geom2d::overlaps(game->GetPlayer()->Hitbox(),geom2d::circle(pos,radius))){ - if(hitList.find(game->GetPlayer())==hitList.end()&&game->GetPlayer()->Hurt(damage,OnUpperLevel(),z)){ - if(!hitsMultiple){ - if(_PlayerHit(&*game->GetPlayer())==BulletDestroyState::DESTROY){ - dead=true; - } - return false; - }else _PlayerHit(&*game->GetPlayer()); - hitList.insert(game->GetPlayer()); - } - } - } - return true; - }; - - while(iterations>0){ - iterations--; - if(IsPlayerAutoAttackProjectile()){pos+=(game->GetWindSpeed()*game->GetElapsedTime())/float(totalIterations);} - pos+=(vel*fElapsedTime)/float(totalIterations); - if(!CollisionCheck()){ - goto DeadBulletCheck; - } - } - pos=finalBulletPos; - if(!CollisionCheck()){ - goto DeadBulletCheck; - } - }else{ - if(IsPlayerAutoAttackProjectile()){pos+=game->GetWindSpeed()*game->GetElapsedTime();} - pos+=vel*fElapsedTime; - } - - DeadBulletCheck: - if(/*World size in PIXELS!*/vi2d worldSize=game->GetCurrentMapData().MapSize*game->GetCurrentMapData().TileSize;pos.x+radius<-WINDOW_SIZE.x||pos.x-radius>worldSize.x+WINDOW_SIZE.x||pos.y+radius<-WINDOW_SIZE.y||pos.y-radius>worldSize.y+WINDOW_SIZE.y){ - dead=true; - return; - } - lifetime-=fElapsedTime; - if(lifetime<=0){ - dead=true; - return; - } -} - -void Bullet::_Draw()const{ - Pixel blendCol=col; - - if(fadeInTime==0&&fadeOutTime==0)blendCol.a=col.a; - else if(fadeOutTime>0)blendCol.a=uint8_t(util::lerp(col.a,0,1-((fadeOutTime-fadeOutTimer)/fadeOutTime))); - else if(fadeInTime>0)blendCol.a=uint8_t(util::lerp(col.a,0,((fadeInTime-fadeInTimer)/fadeInTime))); - - if(GetZ()>0){ - vf2d shadowScale=vf2d{8*scale.x/3.f,1}/std::max(1.f,GetZ()/8); - game->view.DrawDecal(pos-vf2d{3,3}*shadowScale/2+vf2d{0,12*scale.y},GFX["circle.png"].Decal(),shadowScale,BLACK); - } - - const bool NotOnTitleScreen=GameState::STATE!=GameState::states[States::MAIN_MENU]; - - if(NotOnTitleScreen - &&(game->GetPlayer()->HasIframes()||game->GetPlayer()->GetZ()>1)){ - blendCol.a/=1.59f; //Comes from 255 divided by 160 which is roughly what we want the alpha to be when the bullet has full transparency. - } - Draw(blendCol); -} - -void Bullet::Draw(const Pixel blendCol)const{ - if(animated){ - game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GetFrame().GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2+image_angle:image_angle,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,blendCol); - }else{ - game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GFX["circle.png"].Decal(),image_angle,GFX["circle.png"].Sprite()->Size()/2.f,scale,blendCol); - game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GFX["circle_outline.png"].Decal(),image_angle,GFX["circle.png"].Sprite()->Size()/2.f,scale,Pixel{WHITE.r,WHITE.g,WHITE.b,blendCol.a}); - } -} - -BulletDestroyState Bullet::_PlayerHit(Player*player){ - const BulletDestroyState destroyBullet=PlayerHit(player); - if(iframeTimerOnHit>0.f)player->ApplyIframes(iframeTimerOnHit); - if(hitSound)SoundEffect::PlaySFX(hitSound.value(),pos); - return destroyBullet; -} -BulletDestroyState Bullet::_MonsterHit(Monster&monster){ - const BulletDestroyState destroyBullet=MonsterHit(monster); - if(iframeTimerOnHit>0.f)monster.ApplyIframes(iframeTimerOnHit); - if(hitSound)SoundEffect::PlaySFX(hitSound.value(),pos); - return destroyBullet; -} -BulletDestroyState Bullet::PlayerHit(Player*player){ - if(!hitsMultiple)fadeOutTime=0.15f; - return BulletDestroyState::KEEP_ALIVE; -} -BulletDestroyState Bullet::MonsterHit(Monster&monster){ - if(!hitsMultiple)fadeOutTime=0.15f; - return BulletDestroyState::KEEP_ALIVE; -} -bool Bullet::OnUpperLevel(){return upperLevel;} - -const bool Bullet::IsDead()const{ - return dead; -} - -const float Bullet::GetZ()const{ - return z; -} - -Bullet&Bullet::SetIframeTimeOnHit(float iframeTimer){ - iframeTimerOnHit=iframeTimer; - return *this; -} -Bullet&Bullet::SetFadeinTime(float fadeInTime){ - const float durationDiff=fadeInTime-fadeInTimer; - this->fadeInTime=fadeInTime; - lifetime+=durationDiff; - return *this; -} - -const float&Bullet::GetFadeoutTimer()const{ - return fadeOutTimer; -} - -Bullet&Bullet::SetIsPlayerAutoAttackProjectile(){ - playerAutoAttackProjectile=true; - return *this; -} - -const bool Bullet::IsPlayerAutoAttackProjectile()const{ - return playerAutoAttackProjectile; -} - -void Bullet::AddVelocity(vf2d vel){ - this->vel+=vel*game->GetElapsedTime(); -} - -void Bullet::SetBulletType(const BulletType type){ - this->type=type; -} - -const BulletType Bullet::GetBulletType()const{ - return type; -} - -const bool Bullet::IsActivated()const{ - return !IsDeactivated(); -} -const bool Bullet::IsDeactivated()const{ - return deactivated||fadeOutTime>0.f; -} - -void Bullet::Deactivate(){ - deactivated=true; -} - -const double Bullet::GetTimeAlive()const{ - return aliveTime; -} - -const double Bullet::GetAliveTime()const{ - return aliveTime; -} \ No newline at end of file +:IBullet(pos,vel,radius,damage,animation,upperLevel,hitsMultiple,lifetime,rotatesWithAngle,friendly,col,scale,image_angle,hitSound){} +void Bullet::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/Bullet.h b/Adventures in Lestoria/Bullet.h index edca8806..c62a8744 100644 --- a/Adventures in Lestoria/Bullet.h +++ b/Adventures in Lestoria/Bullet.h @@ -30,103 +30,20 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Portions of this software are copyright © 2024 The FreeType +Portions of this software are copyright © 2024 The FreeType Project (www.freetype.org). Please see LICENSE_FT.txt for more information. All rights reserved. */ #pragma endregion #pragma once -#include "Animation.h" -#include "olcUTIL_Animate2D.h" -#include "Monster.h" -#include "DEFINES.h" -using HitList=std::unordered_set>; -enum class BulletType{ - UNDEFINED, - FEATHER, - LARGE_TORNADO, -}; +#include "IBullet.h" -enum class BulletDestroyState{ - KEEP_ALIVE, - DESTROY, -}; - -struct Bullet{ - friend class AiL; - vf2d vel; - vf2d pos; - float radius; - int damage; - Pixel col; - float lifetime=float(INFINITE); - bool hitsMultiple=false; - bool rotates=false; - bool animated=false; - float fadeOutTime=0.f; //Setting the fade out time causes the bullet's lifetime to be set to the fadeout time as well, as that's when the bullet's alpha will reach 0, so it dies. - bool friendly=false; //Whether or not it's a player bullet or enemy bullet. - bool upperLevel=false; - bool alwaysOnTop=false; - float z=0.f; - float image_angle=0.f; -protected: - float distanceTraveled=0.f; - vf2d scale={1,1}; - float fadeInTime=0; //Setting the fade in time causes the bullet to be disabled and the bullet's alpha will fade in from zero to the actual alpha of the bullet. When the fade in timer reaches the fade in time automatically, the bullet will be enabled. - virtual void Update(float fElapsedTime); - void Deactivate(); -private: - float fadeOutTimer=0; - float fadeInTimer=0; - void UpdateFadeTime(float fElapsedTime); - bool dead=false; //When marked as dead it wil be removed by the next frame. - bool simulated=false; //A simulated bullet cannot interact / damage things in the world. It's simply used for simulating the trajectory and potential path of the bullet - float iframeTimerOnHit{0.f}; - bool playerAutoAttackProjectile=false; //Set to true for bullets that are auto attack projectiles to identify them. - void _Draw()const; - BulletType type{BulletType::UNDEFINED}; - bool deactivated{false}; - double aliveTime{}; - float onContactFadeoutTime{}; //What fadeouttime will be set to when the bullet hits a monster. - std::optionalhitSound; -protected: - float drawOffsetY{}; - BulletDestroyState _PlayerHit(Player*player); //Return true to destroy the bullet on hit, return false otherwise. THE BULLET HIT HAS ALREADY OCCURRED. - BulletDestroyState _MonsterHit(Monster&monster); //Return true to destroy the bullet on hit, return false otherwise. THE BULLET HIT HAS ALREADY OCCURRED. - const float&GetFadeoutTimer()const; - void SetBulletType(const BulletType type); - const double GetTimeAlive()const; +class Bullet:public IBullet{ public: - Animate2D::Animationanimation; - Animate2D::AnimationState internal_animState; - HitList hitList; - virtual ~Bullet()=default; Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f); //Initializes a bullet with an animation. Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound=""); - public: - - void SimulateUpdate(const float fElapsedTime); - void _Update(const float fElapsedTime); - //Used by special bullets to control custom despawning behavior! Return true when the bullet should be destroyed. Return false to handle it otherwise (like deactivating it instead). You become responsible for getting rid of the bullet. - //DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! - virtual BulletDestroyState PlayerHit(Player*player); - //Used by special bullets to control custom despawning behavior! Return true when the bullet should be destroyed. Return false to handle it otherwise (like deactivating it instead). You become responsible for getting rid of the bullet. - //DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! - virtual BulletDestroyState MonsterHit(Monster&monster); - Animate2D::Frame GetFrame()const; - virtual void Draw(const Pixel blendCol)const; - bool OnUpperLevel(); - const bool IsDead()const; - const float GetZ()const; - Bullet&SetIframeTimeOnHit(float iframeTimer); - Bullet&SetFadeinTime(float fadeInTime); - Bullet&SetIsPlayerAutoAttackProjectile(); //Enables the playerAutoAttackProjectile flag. - const bool IsPlayerAutoAttackProjectile()const; - void AddVelocity(vf2d vel); - const BulletType GetBulletType()const; - const bool IsActivated()const; - const bool IsDeactivated()const; - const double GetAliveTime()const; +protected: + virtual void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; \ No newline at end of file diff --git a/Adventures in Lestoria/BulletTypes.h b/Adventures in Lestoria/BulletTypes.h index 3ffd95cf..1862d529 100644 --- a/Adventures in Lestoria/BulletTypes.h +++ b/Adventures in Lestoria/BulletTypes.h @@ -46,6 +46,7 @@ struct EnergyBolt:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct FireBolt:public Bullet{ @@ -54,6 +55,7 @@ struct FireBolt:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct LightningBolt:public Bullet{ @@ -62,6 +64,7 @@ struct LightningBolt:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct Arrow:public Bullet{ @@ -77,6 +80,7 @@ struct Arrow:public Bullet{ const vf2d PointToBestTargetPath(const uint8_t perceptionLevel); BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct ChargedArrow:public Bullet{ @@ -85,6 +89,7 @@ struct ChargedArrow:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct FrogTongue:public Bullet{ @@ -98,6 +103,7 @@ struct FrogTongue:public Bullet{ BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! void Draw(const Pixel blendCol)const override; + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct Wisp:public Bullet{ @@ -105,6 +111,7 @@ struct Wisp:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; enum class HorizontalFlip{ @@ -133,6 +140,7 @@ struct DaggerStab:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct DaggerSlash:public Bullet{ @@ -145,6 +153,7 @@ struct DaggerSlash:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct Bomb:public Bullet{ @@ -161,6 +170,7 @@ struct Bomb:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); void Draw(const Pixel blendCol)const override; }; @@ -186,6 +196,7 @@ struct LevitatingRock:public Bullet{ void Draw(const Pixel blendCol)const override; void AssignMaster(LevitatingRock*masterRock); const bool IsMaster()const; + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct Tornado:public Bullet{ @@ -200,6 +211,7 @@ struct Tornado:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct Debris:public Bullet{ @@ -210,6 +222,7 @@ struct Debris:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); void Draw(const Pixel blendCol)const override; }; @@ -222,10 +235,12 @@ struct LargeTornado:public Bullet{ void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct Feather:public Bullet{ Feather(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f); + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); }; struct LargeStone:public Bullet{ @@ -235,6 +250,7 @@ protected: void Update(float fElapsedTime)override; BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); private: const float gravity; const float fixedTimeStep{1/30.f}; @@ -254,6 +270,7 @@ struct FallingStone:public Bullet{ protected: void Update(float fElapsedTime)override; void Draw(const Pixel blendCol)const override; + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); private: const vf2d targetPos; const float zVel{}; @@ -268,6 +285,7 @@ struct DeadlyDash:public Bullet{ DeadlyDash(vf2d startPos,vf2d endPos,float radius,float afterImagesLingeringTime,int damage,float knockbackAmt,bool upperLevel,bool friendly,float afterImagesSpreadDist,const std::string&animation,Pixel col); protected: void Draw(const Pixel blendCol)const override; + void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags); private: const vf2d startPos; const vf2d endPos; diff --git a/Adventures in Lestoria/ChargedArrow.cpp b/Adventures in Lestoria/ChargedArrow.cpp index 2c6ab7cd..27be7fe8 100644 --- a/Adventures in Lestoria/ChargedArrow.cpp +++ b/Adventures in Lestoria/ChargedArrow.cpp @@ -67,4 +67,8 @@ BulletDestroyState ChargedArrow::PlayerHit(Player*player) BulletDestroyState ChargedArrow::MonsterHit(Monster& monster) { return BulletDestroyState::KEEP_ALIVE; +} + +void ChargedArrow::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){ + if(friendly)hurtFlags|=HurtFlag::PLAYER_ABILITY; } \ No newline at end of file diff --git a/Adventures in Lestoria/DEFINES.h b/Adventures in Lestoria/DEFINES.h index 6b088a62..7bca4aca 100644 --- a/Adventures in Lestoria/DEFINES.h +++ b/Adventures in Lestoria/DEFINES.h @@ -50,7 +50,7 @@ using MonsterSpawnerID=int; #define INCLUDE_DAMAGENUMBER_LIST extern std::vector>DAMAGENUMBER_LIST; #define INCLUDE_game extern AiL*game; #define INCLUDE_MONSTER_DATA extern std::mapMONSTER_DATA; -#define INCLUDE_BULLET_LIST extern std::vector>BULLET_LIST; +#define INCLUDE_BULLET_LIST extern std::vector>BULLET_LIST; #define INCLUDE_EMITTER_LIST extern std::vector>EMITTER_LIST; #define INCLUDE_DATA extern utils::datafile DATA; #define INCLUDE_STRATEGY_DATA extern safemap>STRATEGY_DATA; diff --git a/Adventures in Lestoria/DaggerSlash.cpp b/Adventures in Lestoria/DaggerSlash.cpp index b902c997..f63744a4 100644 --- a/Adventures in Lestoria/DaggerSlash.cpp +++ b/Adventures in Lestoria/DaggerSlash.cpp @@ -97,4 +97,6 @@ BulletDestroyState DaggerSlash::MonsterHit(Monster&monster){ monster.Knockback(util::pointTo(sourceMonster.GetPos(),monster.GetPos())*knockbackAmt); Deactivate(); return BulletDestroyState::KEEP_ALIVE; -} \ No newline at end of file +} + +void DaggerSlash::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/DaggerStab.cpp b/Adventures in Lestoria/DaggerStab.cpp index f46dfc32..0863f9fc 100644 --- a/Adventures in Lestoria/DaggerStab.cpp +++ b/Adventures in Lestoria/DaggerStab.cpp @@ -103,4 +103,6 @@ BulletDestroyState DaggerStab::MonsterHit(Monster&monster){ monster.Knockback(util::pointTo(sourceMonster.GetPos(),monster.GetPos())*knockbackAmt); Deactivate(); return BulletDestroyState::KEEP_ALIVE; -} \ No newline at end of file +} + +void DaggerStab::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/DamageNumber.cpp b/Adventures in Lestoria/DamageNumber.cpp index a36e8446..9491af4b 100644 --- a/Adventures in Lestoria/DamageNumber.cpp +++ b/Adventures in Lestoria/DamageNumber.cpp @@ -42,11 +42,13 @@ INCLUDE_game const float DamageNumber::MOVE_UP_TIME=0.4f; +using enum DamageNumberType::DamageNumberType; + DamageNumber::DamageNumber() :damage(0){ } -DamageNumber::DamageNumber(vf2d pos,int damage,bool friendly,DamageNumberType type): +DamageNumber::DamageNumber(vf2d pos,int damage,bool friendly,DamageNumberType::DamageNumberType type): pos(pos),damage(damage),friendly(friendly),type(type),invertedDirection(type==INTERRUPT),riseSpd(20.f){ if(type==INTERRUPT||type==MANA_GAIN)riseSpd=40.f; originalRiseSpd=riseSpd; @@ -63,4 +65,59 @@ void DamageNumber::RecalculateSize(){ size=vf2d{newSize,newSize}; } +} + +void DamageNumber::Draw(){ + #define NumberScalesWithDamage true + #define NormalNumber false + + auto DrawDamageNumber=[&](const bool ScaleWithNumber,std::string_view text,std::paircolorsEnemy,std::paircolorsFriendly,vf2d scaling={1.f,1.f}){ + vf2d textSize=game->GetTextSizeProp(text)*scaling; + if(!friendly){ + vf2d additionalScaling={1.f,1.f}; + if(ScaleWithNumber)additionalScaling=size; + + textSize*=additionalScaling; + vf2d drawPos=pos-textSize/2.f; + + drawPos.x=std::clamp(drawPos.x,game->view.GetWorldTL().x,game->view.GetWorldBR().x-textSize.x); + drawPos.y=std::clamp(drawPos.y,game->view.GetWorldTL().y,game->view.GetWorldBR().y-textSize.y); + + game->view.DrawShadowStringPropDecal(drawPos,text,colorsEnemy.first,colorsEnemy.second,scaling*additionalScaling); + }else{ + vf2d drawPos=pos-textSize/2.f; + drawPos.x=std::clamp(drawPos.x,game->view.GetWorldTL().x,game->view.GetWorldBR().x-textSize.x); + drawPos.y=std::clamp(drawPos.y,game->view.GetWorldTL().y,game->view.GetWorldBR().y-textSize.y); + + game->view.DrawShadowStringPropDecal(drawPos,text,colorsFriendly.first,colorsFriendly.second,scaling); + } + }; + + switch(type){ + case HEALTH_LOSS:{ + std::string text=std::to_string(damage); + DrawDamageNumber(NumberScalesWithDamage,text,{DARK_RED,{255,160,160}},{RED,VERY_DARK_GREY}); + }break; + case HEALTH_GAIN:{ + std::string text="+"+std::to_string(damage); + DrawDamageNumber(NormalNumber,text,{DARK_GREEN,BLACK},{GREEN,VERY_DARK_GREY}); + }break; + case MANA_GAIN:{ + std::string text="+"+std::to_string(damage); + DrawDamageNumber(NormalNumber,text,{BLUE,VERY_DARK_GREY},{BLUE,VERY_DARK_GREY}); + }break; + case INTERRUPT:{ + std::string text="Interrupted!"; + DrawDamageNumber(NormalNumber,text,{BLACK,VERY_DARK_GREY},{BLACK,VERY_DARK_GREY},{0.5f,1}); + }break; + case CRIT:{ + std::string text=std::to_string(damage); + DrawDamageNumber(NumberScalesWithDamage,text,{YELLOW,DARK_YELLOW},{BLACK,{0,0,0,0}}); + }break; + case DOT:{ + std::string text=std::to_string(damage); + DrawDamageNumber(NormalNumber,text,{0xE1BEE7,0x1F083A},{0xDCE775,0x37320A}); + }break; + default:ERR(std::format("Damage Number Type {} is not implemented! THIS SHOULD NOT BE HAPPENING!",int(type))); + } } \ No newline at end of file diff --git a/Adventures in Lestoria/DamageNumber.h b/Adventures in Lestoria/DamageNumber.h index 716e0c14..46a7cb32 100644 --- a/Adventures in Lestoria/DamageNumber.h +++ b/Adventures in Lestoria/DamageNumber.h @@ -38,13 +38,16 @@ All rights reserved. #pragma once #include "olcUTIL_Geometry2D.h" -enum DamageNumberType{ - HEALTH_LOSS, - HEALTH_GAIN, - MANA_GAIN, - INTERRUPT, - CRIT, -}; +namespace DamageNumberType{ + enum DamageNumberType{ + HEALTH_LOSS, + HEALTH_GAIN, + MANA_GAIN, + INTERRUPT, + CRIT, + DOT, + }; +} struct DamageNumber{ vf2d pos; @@ -55,11 +58,12 @@ struct DamageNumber{ vf2d size{1.f,1.f}; bool friendly=false; bool invertedDirection=false; - DamageNumberType type=HEALTH_LOSS; + DamageNumberType::DamageNumberType type=DamageNumberType::HEALTH_LOSS; const static float MOVE_UP_TIME; float originalRiseSpd=0.f; DamageNumber(); //The friendly flag indicates if the number was for a friendly/player target or if it's for a monster target (set to false) - DamageNumber(vf2d pos,int damage,bool friendly=false,DamageNumberType type=HEALTH_LOSS); + DamageNumber(vf2d pos,int damage,bool friendly=false,DamageNumberType::DamageNumberType type=DamageNumberType::HEALTH_LOSS); void RecalculateSize(); + void Draw(); }; \ No newline at end of file diff --git a/Adventures in Lestoria/DeadlyDash.cpp b/Adventures in Lestoria/DeadlyDash.cpp index f8fc286f..2216281c 100644 --- a/Adventures in Lestoria/DeadlyDash.cpp +++ b/Adventures in Lestoria/DeadlyDash.cpp @@ -53,7 +53,7 @@ DeadlyDash::DeadlyDash(vf2d startPos,vf2d endPos,float radius,float afterImagesL const float offsetDist{afterImagesSpreadDist*(i+1)}; const vf2d afterImagePos{geom2d::line{startPos,endPos}.rpoint(offsetDist)}; if(friendly){ - const HurtList&hitList{game->HurtNotHit(afterImagePos,radius,damage,this->hitList,OnUpperLevel(),GetZ(),HurtType::MONSTER)}; + const HurtList&hitList{game->HurtNotHit(afterImagePos,radius,damage,this->hitList,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)}; for(auto&[entityPtr,wasHit]:hitList){ if(wasHit){ Monster*monster{std::get(entityPtr)}; @@ -90,4 +90,9 @@ void DeadlyDash::Draw(const Pixel blendCol)const{ game->view.DrawPartialRotatedDecal(drawPos,frame.GetSourceImage()->Decal(),0.f,frame.GetSourceRect().size/2,frame.GetSourceRect().pos,frame.GetSourceRect().size,{game->GetPlayer()->GetSizeMult(),game->GetPlayer()->GetSizeMult()},{col.r,col.g,col.b,alpha}); game->SetDecalMode(DecalMode::NORMAL); } +} + +void DeadlyDash::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){ + //NOTE: Despite it looking like we modify the damage here, the radius passed to Bullet is 0, so this shouldn't matter anyways, the damage happens in the check in Update() + if(friendly)hurtFlags|=HurtFlag::PLAYER_ABILITY; } \ No newline at end of file diff --git a/Adventures in Lestoria/Debris.cpp b/Adventures in Lestoria/Debris.cpp index 1c862f55..627bc3a8 100644 --- a/Adventures in Lestoria/Debris.cpp +++ b/Adventures in Lestoria/Debris.cpp @@ -60,4 +60,6 @@ BulletDestroyState Debris::MonsterHit(Monster&monster){ } void Debris::Draw(const Pixel blendCol)const{ game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GetFrame().GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2+image_angle:image_angle,{12,12},vf2d{randomFrame*24.f,0.f},{24,24},scale,blendCol); -} \ No newline at end of file +} + +void Debris::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/EnergyBolt.cpp b/Adventures in Lestoria/EnergyBolt.cpp index d601419d..7c90dd66 100644 --- a/Adventures in Lestoria/EnergyBolt.cpp +++ b/Adventures in Lestoria/EnergyBolt.cpp @@ -71,3 +71,7 @@ BulletDestroyState EnergyBolt::MonsterHit(Monster& monster) game->AddEffect(std::make_unique(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Auto Attack.SplashEffectFadeoutTime"_F)); return BulletDestroyState::KEEP_ALIVE; } + +void EnergyBolt::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){ + if(friendly)hurtFlags|=HurtFlag::PLAYER_ABILITY; +} \ No newline at end of file diff --git a/Adventures in Lestoria/FallingStone.cpp b/Adventures in Lestoria/FallingStone.cpp index f68eaaff..1d8a0512 100644 --- a/Adventures in Lestoria/FallingStone.cpp +++ b/Adventures in Lestoria/FallingStone.cpp @@ -87,4 +87,6 @@ void FallingStone::Draw(const Pixel blendCol)const{ indicator.Draw(); } Bullet::Draw(blendCol); -} \ No newline at end of file +} + +void FallingStone::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/Feather.cpp b/Adventures in Lestoria/Feather.cpp index 79249f9e..b8060ba4 100644 --- a/Adventures in Lestoria/Feather.cpp +++ b/Adventures in Lestoria/Feather.cpp @@ -37,8 +37,11 @@ All rights reserved. #pragma endregion #include "BulletTypes.h" +#include "Player.h" Feather::Feather(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool hitsMultiple,float lifetime,bool friendly,Pixel col,vf2d scale,float image_angle) :Bullet(pos,vel,radius,damage,"feather.png",upperLevel,hitsMultiple,lifetime,true,friendly,col){ SetBulletType(BulletType::FEATHER); -} \ No newline at end of file +} + +void Feather::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/FireBolt.cpp b/Adventures in Lestoria/FireBolt.cpp index 1201fe45..fa9955da 100644 --- a/Adventures in Lestoria/FireBolt.cpp +++ b/Adventures in Lestoria/FireBolt.cpp @@ -62,7 +62,7 @@ void FireBolt::Update(float fElapsedTime){ } game->SetupWorldShake("Wizard.Ability 1.WorldShakeTime"_F); if(friendly){ - game->Hurt(pos,"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12,int("Wizard.Ability 1.BulletHitExplosionDamageMult"_F*game->GetPlayer()->GetAttack()),OnUpperLevel(),0,HurtType::MONSTER); + game->Hurt(pos,"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12,int("Wizard.Ability 1.BulletHitExplosionDamageMult"_F*game->GetPlayer()->GetAttack()),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY); }else{ if(geom2d::overlaps(geom2d::circle{pos,"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12},geom2d::circle{game->GetPlayer()->GetPos(),12.f})){ game->GetPlayer()->Hurt(damage,OnUpperLevel(),0.f); @@ -94,9 +94,13 @@ BulletDestroyState FireBolt::MonsterHit(Monster& monster) game->AddEffect(std::make_unique(monster.GetPos(),"Wizard.Ability 1.BulletHitExplosionParticleLifetimeRange"_FRange,"circle.png",upperLevel,"Wizard.Ability 1.BulletHitExplosionParticleSizeRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleFadeoutTimeRange"_FRange,vf2d{"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange},Pixel{uint8_t("Wizard.Ability 1.BulletHitExplosionParticleRedRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleGreenRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleBlueRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleAlphaRange"_FRange)})); } game->SetupWorldShake("Wizard.Ability 1.WorldShakeTime"_F); - game->Hurt(monster.GetPos(),"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12,int("Wizard.Ability 1.BulletHitExplosionDamageMult"_F*game->GetPlayer()->GetAttack()),OnUpperLevel(),0,HurtType::MONSTER); + game->Hurt(monster.GetPos(),"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12,int("Wizard.Ability 1.BulletHitExplosionDamageMult"_F*game->GetPlayer()->GetAttack()),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY); game->AddEffect(std::make_unique(monster.GetPos(),0,"splash_effect.png",upperLevel,"Wizard.Ability 1.BulletHitExplosionRange"_F/100*2,"Wizard.Ability 1.BulletHitExplosionFadeoutTime"_F,vf2d{},"Wizard.Ability 1.BulletHitExplosionColor"_Pixel)); SoundEffect::PlaySFX("Wizard Fire Bolt Hit",pos); return BulletDestroyState::KEEP_ALIVE; } + +void FireBolt::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){ + if(friendly)hurtFlags|=HurtFlag::PLAYER_ABILITY; +} \ No newline at end of file diff --git a/Adventures in Lestoria/FrogTongue.cpp b/Adventures in Lestoria/FrogTongue.cpp index d2a44c8e..b86dec88 100644 --- a/Adventures in Lestoria/FrogTongue.cpp +++ b/Adventures in Lestoria/FrogTongue.cpp @@ -98,4 +98,8 @@ void FrogTongue::Draw(const Pixel blendCol)const{ 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); +} + +void FrogTongue::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){ + if(friendly)hurtFlags|=HurtFlag::PLAYER_ABILITY; } \ No newline at end of file diff --git a/Adventures in Lestoria/HurtDamageInfo.h b/Adventures in Lestoria/HurtDamageInfo.h new file mode 100644 index 00000000..fa6b36aa --- /dev/null +++ b/Adventures in Lestoria/HurtDamageInfo.h @@ -0,0 +1,59 @@ +#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 © 2024 The FreeType +Project (www.freetype.org). Please see LICENSE_FT.txt for more information. +All rights reserved. +*/ +#pragma endregion +#pragma once + +namespace HurtFlag{ + enum HurtFlag{ + NONE = 0b00, + PLAYER_ABILITY = 0b01, //Specifically use this flag for player auto attacks or Abilities only! (Identifies these attacks for the Trapper class) + DOT = 0b10, //Damage over time ability. + }; +} + +enum class TrueDamageFlag{ + NORMAL_DAMAGE, + IGNORE_DAMAGE_RULES, //Deals true damage, ignoring established invulnerability/iframe rules. Will never miss and will not have its damage modified by any buffs/stats. +}; + +struct HurtDamageInfo{ + int damage{}; + bool onUpperLevel{false}; + float z{}; + HurtFlag::HurtFlag hurtFlags{HurtFlag::NONE}; + TrueDamageFlag damageRule{TrueDamageFlag::NORMAL_DAMAGE}; +}; \ No newline at end of file diff --git a/Adventures in Lestoria/IBullet.cpp b/Adventures in Lestoria/IBullet.cpp new file mode 100644 index 00000000..858324dc --- /dev/null +++ b/Adventures in Lestoria/IBullet.cpp @@ -0,0 +1,282 @@ +#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 © 2024 The FreeType +Project (www.freetype.org). Please see LICENSE_FT.txt for more information. +All rights reserved. +*/ +#pragma endregion +#include "Bullet.h" +#include "AdventuresInLestoria.h" +#include "DEFINES.h" +#include "safemap.h" +#include "util.h" +#include "SoundEffect.h" + +INCLUDE_ANIMATION_DATA +INCLUDE_game +INCLUDE_GFX +INCLUDE_MONSTER_LIST +INCLUDE_WINDOW_SIZE + +IBullet::IBullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale,float image_angle) + :pos(pos),vel(vel),radius(radius),damage(damage),col(col),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle){}; + +IBullet::IBullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound) + :pos(pos),vel(vel),radius(radius),damage(damage),col(col),animated(true),rotates(rotatesWithAngle),lifetime(lifetime),hitsMultiple(hitsMultiple),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle),hitSound(std::string(hitSound)){ + this->animation.AddState(animation,ANIMATION_DATA.at(animation)); + this->animation.ChangeState(internal_animState,animation); +}; + +Animate2D::Frame IBullet::GetFrame()const{ + return animation.GetFrame(internal_animState); +} +void IBullet::UpdateFadeTime(float fElapsedTime){ + aliveTime+=fElapsedTime; + if(fadeInTime>0){ + if(fadeInTimer0){ + if(fadeOutTimer==0){ + lifetime=fadeOutTime; + } + fadeOutTimer+=fElapsedTime; + } +} + +void IBullet::Update(float fElapsedTime){} + +void IBullet::SimulateUpdate(const float fElapsedTime){ + simulated=true; + _Update(fElapsedTime); + simulated=false; +} + +void IBullet::_Update(const float fElapsedTime){ + if(dead)return; + UpdateFadeTime(fElapsedTime); + Update(fElapsedTime); + animation.UpdateState(internal_animState,fElapsedTime); + const bool CollisionCheckRequired=IsActivated()&&fadeInTimer==fadeInTime&&radius!=0.f; + if(CollisionCheckRequired){ + float totalDistance=(vel*fElapsedTime).mag(); + int iterations=int(std::max(1.f,(vel*fElapsedTime).mag())); + int totalIterations=iterations; + vf2d finalBulletPos=pos+vel*fElapsedTime; + if(IsPlayerAutoAttackProjectile()){finalBulletPos+=(game->GetWindSpeed()*game->GetElapsedTime())/float(totalIterations);} + distanceTraveled+=totalDistance/24.f*100.f; + const auto CollisionCheck=[&](){ + if(simulated)return true; + if(friendly){ + for(std::unique_ptr&m:MONSTER_LIST){ + if(geom2d::overlaps(m->BulletCollisionHitbox(),geom2d::circle(pos,radius))){ + if(hitList.find(&*m)==hitList.end()){ + HurtDamageInfo damageData{damage,OnUpperLevel(),z,HurtFlag::NONE}; + ModifyOutgoingDamageData(damageData.damage,damageData.onUpperLevel,damageData.z,damageData.hurtFlags); + if(m->Hurt(damageData)){ + if(!hitsMultiple){ + if(_MonsterHit(*m)==BulletDestroyState::DESTROY){ + dead=true; + } + return false; + }else _MonsterHit(*m); + hitList.insert(&*m); + } + } + } + } + } else { + if(geom2d::overlaps(game->GetPlayer()->Hitbox(),geom2d::circle(pos,radius))){ + if(hitList.find(game->GetPlayer())==hitList.end()){ + HurtDamageInfo damageData{damage,OnUpperLevel(),z,HurtFlag::NONE}; + //NOTE: OnHurt() will potentially change the Damage Data, passing it along to bullet children that might need to modify the flags! + ModifyOutgoingDamageData(damageData.damage,damageData.onUpperLevel,damageData.z,damageData.hurtFlags); + if(game->GetPlayer()->Hurt(damageData)){ + if(!hitsMultiple){ + if(_PlayerHit(&*game->GetPlayer())==BulletDestroyState::DESTROY){ + dead=true; + } + return false; + }else _PlayerHit(&*game->GetPlayer()); + hitList.insert(game->GetPlayer()); + } + } + } + } + return true; + }; + + while(iterations>0){ + iterations--; + if(IsPlayerAutoAttackProjectile()){pos+=(game->GetWindSpeed()*game->GetElapsedTime())/float(totalIterations);} + pos+=(vel*fElapsedTime)/float(totalIterations); + if(!CollisionCheck()){ + goto DeadBulletCheck; + } + } + pos=finalBulletPos; + if(!CollisionCheck()){ + goto DeadBulletCheck; + } + }else{ + if(IsPlayerAutoAttackProjectile()){pos+=game->GetWindSpeed()*game->GetElapsedTime();} + pos+=vel*fElapsedTime; + } + + DeadBulletCheck: + if(/*World size in PIXELS!*/vi2d worldSize=game->GetCurrentMapData().MapSize*game->GetCurrentMapData().TileSize;pos.x+radius<-WINDOW_SIZE.x||pos.x-radius>worldSize.x+WINDOW_SIZE.x||pos.y+radius<-WINDOW_SIZE.y||pos.y-radius>worldSize.y+WINDOW_SIZE.y){ + dead=true; + return; + } + lifetime-=fElapsedTime; + if(lifetime<=0){ + dead=true; + return; + } +} + +void IBullet::_Draw()const{ + Pixel blendCol=col; + + if(fadeInTime==0&&fadeOutTime==0)blendCol.a=col.a; + else if(fadeOutTime>0)blendCol.a=uint8_t(util::lerp(col.a,0,1-((fadeOutTime-fadeOutTimer)/fadeOutTime))); + else if(fadeInTime>0)blendCol.a=uint8_t(util::lerp(col.a,0,((fadeInTime-fadeInTimer)/fadeInTime))); + + if(GetZ()>0){ + vf2d shadowScale=vf2d{8*scale.x/3.f,1}/std::max(1.f,GetZ()/8); + game->view.DrawDecal(pos-vf2d{3,3}*shadowScale/2+vf2d{0,12*scale.y},GFX["circle.png"].Decal(),shadowScale,BLACK); + } + + const bool NotOnTitleScreen=GameState::STATE!=GameState::states[States::MAIN_MENU]; + + if(NotOnTitleScreen + &&(game->GetPlayer()->HasIframes()||game->GetPlayer()->GetZ()>1)){ + blendCol.a/=1.59f; //Comes from 255 divided by 160 which is roughly what we want the alpha to be when the bullet has full transparency. + } + Draw(blendCol); +} + +void IBullet::Draw(const Pixel blendCol)const{ + if(animated){ + game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GetFrame().GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2+image_angle:image_angle,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,blendCol); + }else{ + game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GFX["circle.png"].Decal(),image_angle,GFX["circle.png"].Sprite()->Size()/2.f,scale,blendCol); + game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GFX["circle_outline.png"].Decal(),image_angle,GFX["circle.png"].Sprite()->Size()/2.f,scale,Pixel{WHITE.r,WHITE.g,WHITE.b,blendCol.a}); + } +} + +BulletDestroyState IBullet::_PlayerHit(Player*player){ + const BulletDestroyState destroyBullet=PlayerHit(player); + if(iframeTimerOnHit>0.f)player->ApplyIframes(iframeTimerOnHit); + if(hitSound)SoundEffect::PlaySFX(hitSound.value(),pos); + return destroyBullet; +} +BulletDestroyState IBullet::_MonsterHit(Monster&monster){ + const BulletDestroyState destroyBullet=MonsterHit(monster); + if(iframeTimerOnHit>0.f)monster.ApplyIframes(iframeTimerOnHit); + if(hitSound)SoundEffect::PlaySFX(hitSound.value(),pos); + return destroyBullet; +} +BulletDestroyState IBullet::PlayerHit(Player*player){ + if(!hitsMultiple)fadeOutTime=0.15f; + return BulletDestroyState::KEEP_ALIVE; +} +BulletDestroyState IBullet::MonsterHit(Monster&monster){ + if(!hitsMultiple)fadeOutTime=0.15f; + return BulletDestroyState::KEEP_ALIVE; +} +bool IBullet::OnUpperLevel(){return upperLevel;} + +const bool IBullet::IsDead()const{ + return dead; +} + +const float IBullet::GetZ()const{ + return z; +} + +IBullet&IBullet::SetIframeTimeOnHit(float iframeTimer){ + iframeTimerOnHit=iframeTimer; + return *this; +} +IBullet&IBullet::SetFadeinTime(float fadeInTime){ + const float durationDiff=fadeInTime-fadeInTimer; + this->fadeInTime=fadeInTime; + lifetime+=durationDiff; + return *this; +} + +const float&IBullet::GetFadeoutTimer()const{ + return fadeOutTimer; +} + +IBullet&IBullet::SetIsPlayerAutoAttackProjectile(){ + playerAutoAttackProjectile=true; + return *this; +} + +const bool IBullet::IsPlayerAutoAttackProjectile()const{ + return playerAutoAttackProjectile; +} + +void IBullet::AddVelocity(vf2d vel){ + this->vel+=vel*game->GetElapsedTime(); +} + +void IBullet::SetBulletType(const BulletType type){ + this->type=type; +} + +const BulletType IBullet::GetBulletType()const{ + return type; +} + +const bool IBullet::IsActivated()const{ + return !IsDeactivated(); +} +const bool IBullet::IsDeactivated()const{ + return deactivated||fadeOutTime>0.f; +} + +void IBullet::Deactivate(){ + deactivated=true; +} + +const double IBullet::GetTimeAlive()const{ + return aliveTime; +} + +const double IBullet::GetAliveTime()const{ + return aliveTime; +} \ No newline at end of file diff --git a/Adventures in Lestoria/IBullet.h b/Adventures in Lestoria/IBullet.h new file mode 100644 index 00000000..2b409f7e --- /dev/null +++ b/Adventures in Lestoria/IBullet.h @@ -0,0 +1,133 @@ +#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 © 2024 The FreeType +Project (www.freetype.org). Please see LICENSE_FT.txt for more information. +All rights reserved. +*/ +#pragma endregion +#pragma once +#include "Animation.h" +#include "olcUTIL_Animate2D.h" +#include "Monster.h" +#include "DEFINES.h" +using HitList=std::unordered_set>; + +enum class BulletType{ + UNDEFINED, + FEATHER, + LARGE_TORNADO, +}; + +enum class BulletDestroyState{ + KEEP_ALIVE, + DESTROY, +}; + +struct IBullet{ + friend class AiL; + vf2d vel; + vf2d pos; + float radius; + int damage; + Pixel col; + float lifetime=float(INFINITE); + bool hitsMultiple=false; + bool rotates=false; + bool animated=false; + float fadeOutTime=0.f; //Setting the fade out time causes the bullet's lifetime to be set to the fadeout time as well, as that's when the bullet's alpha will reach 0, so it dies. + bool friendly=false; //Whether or not it's a player bullet or enemy bullet. + bool upperLevel=false; + bool alwaysOnTop=false; + float z=0.f; + float image_angle=0.f; +protected: + float distanceTraveled=0.f; + vf2d scale={1,1}; + float fadeInTime=0; //Setting the fade in time causes the bullet to be disabled and the bullet's alpha will fade in from zero to the actual alpha of the bullet. When the fade in timer reaches the fade in time automatically, the bullet will be enabled. + virtual void Update(float fElapsedTime); + void Deactivate(); +private: + float fadeOutTimer=0; + float fadeInTimer=0; + void UpdateFadeTime(float fElapsedTime); + bool dead=false; //When marked as dead it wil be removed by the next frame. + bool simulated=false; //A simulated bullet cannot interact / damage things in the world. It's simply used for simulating the trajectory and potential path of the bullet + float iframeTimerOnHit{0.f}; + bool playerAutoAttackProjectile=false; //Set to true for bullets that are auto attack projectiles to identify them. + void _Draw()const; + BulletType type{BulletType::UNDEFINED}; + bool deactivated{false}; + double aliveTime{}; + float onContactFadeoutTime{}; //What fadeouttime will be set to when the bullet hits a monster. + std::optionalhitSound; +protected: + float drawOffsetY{}; + BulletDestroyState _PlayerHit(Player*player); //Return true to destroy the bullet on hit, return false otherwise. THE BULLET HIT HAS ALREADY OCCURRED. + BulletDestroyState _MonsterHit(Monster&monster); //Return true to destroy the bullet on hit, return false otherwise. THE BULLET HIT HAS ALREADY OCCURRED. + const float&GetFadeoutTimer()const; + void SetBulletType(const BulletType type); + const double GetTimeAlive()const; +public: + Animate2D::Animationanimation; + Animate2D::AnimationState internal_animState; + HitList hitList; + virtual ~IBullet()=default; + IBullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f); + //Initializes a bullet with an animation. + IBullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound=""); + public: + + void SimulateUpdate(const float fElapsedTime); + void _Update(const float fElapsedTime); + //Used by special bullets to control custom despawning behavior! Return true when the bullet should be destroyed. Return false to handle it otherwise (like deactivating it instead). You become responsible for getting rid of the bullet. + //DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + virtual BulletDestroyState PlayerHit(Player*player); + //Used by special bullets to control custom despawning behavior! Return true when the bullet should be destroyed. Return false to handle it otherwise (like deactivating it instead). You become responsible for getting rid of the bullet. + //DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! + virtual BulletDestroyState MonsterHit(Monster&monster); + Animate2D::Frame GetFrame()const; + virtual void Draw(const Pixel blendCol)const; + bool OnUpperLevel(); + const bool IsDead()const; + const float GetZ()const; + IBullet&SetIframeTimeOnHit(float iframeTimer); + IBullet&SetFadeinTime(float fadeInTime); + IBullet&SetIsPlayerAutoAttackProjectile(); //Enables the playerAutoAttackProjectile flag. + const bool IsPlayerAutoAttackProjectile()const; + void AddVelocity(vf2d vel); + const BulletType GetBulletType()const; + const bool IsActivated()const; + const bool IsDeactivated()const; + const double GetAliveTime()const; + virtual void ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags)=0; +}; \ No newline at end of file diff --git a/Adventures in Lestoria/LargeStone.cpp b/Adventures in Lestoria/LargeStone.cpp index 8cc864b2..e70d4b3e 100644 --- a/Adventures in Lestoria/LargeStone.cpp +++ b/Adventures in Lestoria/LargeStone.cpp @@ -97,4 +97,6 @@ BulletDestroyState LargeStone::PlayerHit(Player*player){ BulletDestroyState LargeStone::MonsterHit(Monster&monster){ return BulletDestroyState::KEEP_ALIVE; -} \ No newline at end of file +} + +void LargeStone::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/LargeTornado.cpp b/Adventures in Lestoria/LargeTornado.cpp index 4414650c..0c7d0079 100644 --- a/Adventures in Lestoria/LargeTornado.cpp +++ b/Adventures in Lestoria/LargeTornado.cpp @@ -73,4 +73,6 @@ BulletDestroyState LargeTornado::MonsterHit(Monster&monster){ monster.ApplyIframes(knockupDuration*2); return BulletDestroyState::KEEP_ALIVE; -} \ No newline at end of file +} + +void LargeTornado::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/LevitatingRock.cpp b/Adventures in Lestoria/LevitatingRock.cpp index 15fb7331..a5bca037 100644 --- a/Adventures in Lestoria/LevitatingRock.cpp +++ b/Adventures in Lestoria/LevitatingRock.cpp @@ -112,4 +112,6 @@ void LevitatingRock::AssignMaster(LevitatingRock*masterRock){ const bool LevitatingRock::IsMaster()const{ return slaveRocks.size()>0; -} \ No newline at end of file +} + +void LevitatingRock::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/LightningBolt.cpp b/Adventures in Lestoria/LightningBolt.cpp index c4a03048..e6608324 100644 --- a/Adventures in Lestoria/LightningBolt.cpp +++ b/Adventures in Lestoria/LightningBolt.cpp @@ -96,7 +96,7 @@ BulletDestroyState LightningBolt::MonsterHit(Monster& monster) geom2d::linelineToTarget=geom2d::line(monster.GetPos(),m->GetPos()); float dist=lineToTarget.length(); if(dist<="Wizard.Ability 2.LightningChainRadius"_F/100*24){ - if(m->Hurt(int(game->GetPlayer()->GetAttack()*"Wizard.Ability 2.LightningChainDamageMult"_F),OnUpperLevel(),0)){ + if(m->Hurt(int(game->GetPlayer()->GetAttack()*"Wizard.Ability 2.LightningChainDamageMult"_F),OnUpperLevel(),0,HurtFlag::PLAYER_ABILITY)){ EMITTER_LIST.push_back(std::make_unique(LightningBoltEmitter(monster.GetPos(),m->GetPos(),"Wizard.Ability 2.LightningChainFrequency"_F,"Wizard.Ability 2.LightningChainLifetime"_F,upperLevel))); game->AddEffect(std::make_unique(m->GetPos(),"Wizard.Ability 2.LightningChainSplashLifetime"_F,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Ability 2.LightningChainSplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.LightningChainSplashRotationRange"_FRange)); targetsHit++; @@ -107,4 +107,8 @@ BulletDestroyState LightningBolt::MonsterHit(Monster& monster) SoundEffect::PlaySFX("Wizard Lightning Bolt Hit",pos); return BulletDestroyState::KEEP_ALIVE; +} + +void LightningBolt::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){ + if(friendly)hurtFlags|=HurtFlag::PLAYER_ABILITY; } \ No newline at end of file diff --git a/Adventures in Lestoria/Meteor.cpp b/Adventures in Lestoria/Meteor.cpp index 1ab47916..87b23753 100644 --- a/Adventures in Lestoria/Meteor.cpp +++ b/Adventures in Lestoria/Meteor.cpp @@ -63,7 +63,7 @@ bool Meteor::Update(float fElapsedTime){ vf2d effectPos=vf2d{cos(randomAngle),sin(randomAngle)}*randomRange+meteorOffset; game->AddEffect(std::make_unique(effectPos,0,"circle.png",OnUpperLevel(),vf2d{util::random(2)+1,util::random(3)+1},util::random(3)+1,vf2d{util::random(10)-5,-util::random(20)-5},Pixel{255,uint8_t(randomColorTintG),uint8_t(randomColorTintB),uint8_t("Wizard.Ability 3.MeteorImpactParticleAlphaRange"_FRange)},0,0,true),effectPos.yHurt(pos,"Wizard.Ability 3.MeteorRadius"_F/100*24,int(game->GetPlayer()->GetAttack()*"Wizard.Ability 3.MeteorDamageMult"_F),OnUpperLevel(),0,HurtType::MONSTER); + game->Hurt(pos,"Wizard.Ability 3.MeteorRadius"_F/100*24,int(game->GetPlayer()->GetAttack()*"Wizard.Ability 3.MeteorDamageMult"_F),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY); game->AddEffect(std::make_unique(pos,"Wizard.Ability 3.FireRingLifetime"_F,"fire_ring1.png",OnUpperLevel(),vf2d{"Wizard.Ability 3.MeteorRadius"_F/100*2,"Wizard.Ability 3.MeteorRadius"_F/100*2},"Wizard.Ability 3.FireRingFadeoutTime"_F),true); SoundEffect::PlaySFX("Wizard Meteor",pos); } diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index b59de7b4..a678ff5c 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -601,37 +601,57 @@ const bool Monster::AttackAvoided(const float attackZ)const{ return HasIframes()||abs(GetZ()-attackZ)>1; } -bool Monster::Hurt(int damage,bool onUpperLevel,float z){ - return _Hurt(damage,onUpperLevel,z,TrueDamageFlag::NORMAL_DAMAGE); +bool Monster::Hurt(HurtDamageInfo damageData){ + return _Hurt(damageData.damage,damageData.onUpperLevel,damageData.z,damageData.damageRule,damageData.hurtFlags); } -bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule){ - const bool TrueDamage=damageRule==TrueDamageFlag::IGNORE_DAMAGE_RULES; + +bool Monster::Hurt(int damage,bool onUpperLevel,float z,HurtFlag::HurtFlag hurtFlags){ + return _Hurt(damage,onUpperLevel,z,TrueDamageFlag::NORMAL_DAMAGE,hurtFlags); +} +bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule,HurtFlag::HurtFlag hurtFlags){ + const bool TriggersMark{bool(hurtFlags&HurtFlag::PLAYER_ABILITY)}; + + const bool TrueDamage{damageRule==TrueDamageFlag::IGNORE_DAMAGE_RULES}; + const bool IsDOT{bool(hurtFlags&HurtFlag::DOT)}; + + const bool NormalDamageCalculationRequired{!IsDOT&&!TrueDamage}; + const bool PlayHitSoundEffect{!IsDOT}; + if(!TrueDamage&&(Invulnerable()||!IsAlive()||onUpperLevel!=OnUpperLevel()||AttackAvoided(z)))return false; if(game->InBossEncounter()){ game->StartBossEncounter(); } game->GetPlayer()->ResetLastCombatTime(); float mod_dmg=float(damage); + + bool crit{false}; + if(NormalDamageCalculationRequired){ + #pragma region Handle Crits + if(util::random(1)GetPlayer()->GetCritRatePct()){ + mod_dmg*=1+game->GetPlayer()->GetCritDmgPct(); + crit=true; + } + #pragma endregion - #pragma region Handle Crits - bool crit=false; - if(util::random(1)GetPlayer()->GetCritRatePct()){ - mod_dmg*=1+game->GetPlayer()->GetCritDmgPct(); - crit=true; - } - #pragma endregion + mod_dmg-=mod_dmg*GetDamageReductionFromBuffs(); + } - mod_dmg-=mod_dmg*GetDamageReductionFromBuffs(); + if(TriggersMark&&GetMarkStacks()>0)Hurt(game->GetPlayer()->GetAttack()*"Trapper.Ability 1.Damage Increase Bonus"_F/100.f,OnUpperLevel(),GetZ(),HurtFlag::DOT); mod_dmg=std::ceil(mod_dmg); - if(TrueDamage){ - mod_dmg=damage; //True damage override, ignore all damage changes. - crit=false; //True damage disables critting. - } - hp=std::max(0,hp-int(mod_dmg)); + if(IsDOT){ + if(lastDotTimer>0){ + dotNumberPtr.get()->damage+=int(mod_dmg); + dotNumberPtr.get()->RecalculateSize(); + }else{ + dotNumberPtr=std::make_shared(pos-vf2d{0,GetCollisionRadius()/2.f},int(mod_dmg),false,DamageNumberType::DOT); + DAMAGENUMBER_LIST.push_back(dotNumberPtr); + } + lastDotTimer=0.05f; + }else if(lastHitTimer>0){ damageNumberPtr.get()->damage+=int(mod_dmg); damageNumberPtr.get()->pauseTime=0.4f; @@ -642,7 +662,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da } #pragma region Change Label to Crit if(crit){ - damageNumberPtr.get()->type=CRIT; + damageNumberPtr.get()->type=DamageNumberType::CRIT; } #pragma endregion lastHitTimer=0.05f; @@ -650,12 +670,10 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da attackedByPlayer=true; if(!IsAlive()){ OnDeath(); - SoundEffect::PlaySFX(GetDeathSound(),GetPos()); }else{ hp=std::max(1,hp); //Make sure it stays alive if it's supposed to be alive... - - if(monsterHurtSoundCooldown==0.f){ + if(PlayHitSoundEffect&&monsterHurtSoundCooldown==0.f){ monsterHurtSoundCooldown=util::random(0.5f)+0.5f; SoundEffect::PlaySFX(GetHurtSound(),GetPos()); } @@ -1150,8 +1168,8 @@ const bool Monster::IsSolid()const{ return Immovable(); } -void Monster::_DealTrueDamage(const uint32_t damageAmt){ - _Hurt(damageAmt,OnUpperLevel(),GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES); +void Monster::_DealTrueDamage(const uint32_t damageAmt,HurtFlag::HurtFlag hurtFlags){ + _Hurt(damageAmt,OnUpperLevel(),GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES,hurtFlags); } void Monster::Heal(const int healAmt){ @@ -1176,4 +1194,13 @@ const float Monster::GetModdedStatBonuses(std::string_view stat)const{ const std::optional>&Monster::GetRectangleCollision()const{ return MONSTER_DATA.at(GetName()).GetRectangleCollision(); +} + +const int Monster::GetMarkStacks()const{ + const std::vector&markBuffs{GetBuffs(BuffType::TRAPPER_MARK)}; + int stackCount{}; + for(const Buff&b:markBuffs){ + stackCount+=b.intensity; + } + return stackCount; } \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index 066ca11a..91e2f739 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -48,6 +48,7 @@ All rights reserved. #include "TMXParser.h" #include "MonsterData.h" #include "Direction.h" +#include "HurtDamageInfo.h" INCLUDE_ITEM_DATA INCLUDE_MONSTER_DATA @@ -59,11 +60,6 @@ enum class Attribute; class GameEvent; -enum class TrueDamageFlag{ - NORMAL_DAMAGE, - IGNORE_DAMAGE_RULES, //Deals true damage, ignoring established invulnerability/iframe rules. Will never miss and will not have its damage modified by any buffs/stats. -}; - namespace MonsterTests{ class MonsterTest; }; @@ -98,7 +94,10 @@ public: bool Update(float fElapsedTime); //Returns true when damage is actually dealt. Provide whether or not the attack is on the upper level or not. Monsters must be on the same level to get hit by it. (there is a death check and level check here.) //If you need to hurt multiple enemies try AiL::HurtEnemies() - bool Hurt(int damage,bool onUpperLevel,float z); + bool Hurt(HurtDamageInfo damageData); + //Returns true when damage is actually dealt. Provide whether or not the attack is on the upper level or not. Monsters must be on the same level to get hit by it. (there is a death check and level check here.) + //If you need to hurt multiple enemies try AiL::HurtEnemies() + bool Hurt(int damage,bool onUpperLevel,float z,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE); const bool IsAlive()const; const vf2d&GetTargetPos()const; Direction GetFacingDirection()const; @@ -195,11 +194,12 @@ public: const bool HasArrowIndicator()const; const bool ReachedTargetPos(const float maxDistanceFromTarget=4.f)const; const float GetHealthRatio()const; - void _DealTrueDamage(const uint32_t damageAmt); + void _DealTrueDamage(const uint32_t damageAmt,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE); void Heal(const int healAmt); const float GetModdedStatBonuses(std::string_view stat)const; //The collision rectangle is only used currently for determining the safe spots for the stone golem boss fight. const std::optional>&GetRectangleCollision()const; + const int GetMarkStacks()const; //Number of Trapper marks on this target. private: //NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!! // The way this works is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop. @@ -240,8 +240,10 @@ private: Pathfinding::sSpline path; float pathIndex=0; float lastHitTimer=0; + float lastDotTimer=0; float spriteRot=0; std::shared_ptrdamageNumberPtr; + std::shared_ptrdotNumberPtr; int phase=0; bool diesNormally=true; //If set to false, the monster death is handled in a special way. Set it to true when it's time to die. float targetSize=0; @@ -278,7 +280,7 @@ private: float fadeTimer{0.f}; bool markedForDeletion{false}; //DO NOT MODIFY DIRECTLY. Use MarkForDeletion() if this monster needs to be marked. NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!! float solidFadeTimer{0.f}; - bool _Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule); + bool _Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule,HurtFlag::HurtFlag hurtFlags); private: struct STRATEGY{ static std::string ERR; diff --git a/Adventures in Lestoria/MonsterData.cpp b/Adventures in Lestoria/MonsterData.cpp index 9ad90841..601007ba 100644 --- a/Adventures in Lestoria/MonsterData.cpp +++ b/Adventures in Lestoria/MonsterData.cpp @@ -188,7 +188,7 @@ void MonsterData::InitializeMonsterData(){ if(DATA["Monsters"][MonsterName].HasProperty("Invulnerable"))monster.invulnerable=DATA["Monsters"][MonsterName]["Invulnerable"].GetBool(); if(DATA["Monsters"][MonsterName].HasProperty("Lifetime"))monster.lifetime=DATA["Monsters"][MonsterName]["Lifetime"].GetReal(); - monster.collisionRadius=8; + monster.collisionRadius=8*(std::min(DATA["Monsters"][MonsterName]["SheetFrameSize"].GetInt(0),DATA["Monsters"][MonsterName]["SheetFrameSize"].GetInt(1))/24.f); if(DATA["Monsters"][MonsterName].HasProperty("Collision Radius"))monster.collisionRadius=DATA["Monsters"][MonsterName]["Collision Radius"].GetReal(); if(DATA["Monsters"][MonsterName].HasProperty("ShowBossIndicator"))monster.hasArrowIndicator=DATA["Monsters"][MonsterName]["ShowBossIndicator"].GetBool(); diff --git a/Adventures in Lestoria/Pixel.cpp b/Adventures in Lestoria/Pixel.cpp index 24a75763..71a9e6ef 100644 --- a/Adventures in Lestoria/Pixel.cpp +++ b/Adventures in Lestoria/Pixel.cpp @@ -63,8 +63,8 @@ namespace olc{ Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { n = red | (green << 8) | (blue << 16) | (alpha << 24); } // Thanks jarekpelczar - Pixel::Pixel(uint32_t p) - { n = p; } + Pixel::Pixel(uint32_t hex) + { n=((hex&0xFF0000)>>16)|(hex&0x00FF00)|((hex&0x0000FF)<<16)|0xFF000000; } bool Pixel::operator==(const Pixel& p) const { return n == p.n; } @@ -168,5 +168,11 @@ namespace olc{ Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t) { return (p2 * t) + p1 * (1.0f - t); } + Pixel PixelRaw(uint32_t n){ + Pixel newPixel{}; + newPixel.n=n; + return newPixel; + } + #endif } \ No newline at end of file diff --git a/Adventures in Lestoria/Pixel.h b/Adventures in Lestoria/Pixel.h index 7063816b..82bdbb8b 100644 --- a/Adventures in Lestoria/Pixel.h +++ b/Adventures in Lestoria/Pixel.h @@ -72,7 +72,8 @@ namespace olc{ Pixel(); Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = nDefaultAlpha); - Pixel(uint32_t p); + //Hex Implicit Constructor!! ALPHA IS ASSUMED TO BE 255! If you need access to modifying the raw value, use PixelRaw() + Pixel(uint32_t hex); Pixel& operator = (const Pixel& v) = default; bool operator ==(const Pixel& p) const; bool operator !=(const Pixel& p) const; @@ -91,6 +92,7 @@ namespace olc{ Pixel PixelF(float red, float green, float blue, float alpha = 1.0f); Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t); + Pixel PixelRaw(uint32_t n); // O------------------------------------------------------------------------------O diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index bc6fa354..2fa441d3 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -424,7 +424,7 @@ void Player::Update(float fElapsedTime){ spin_angle=0; z=0; float numb=4; - const HurtList&hitEnemies=game->Hurt(pos,"Warrior.Ability 2.Range"_F/100*12,int(GetAttack()*"Warrior.Ability 2.DamageMult"_F),OnUpperLevel(),0,HurtType::MONSTER); + const HurtList&hitEnemies=game->Hurt(pos,"Warrior.Ability 2.Range"_F/100*12,int(GetAttack()*"Warrior.Ability 2.DamageMult"_F),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY); #pragma region Knockback effect for(auto&[targetPtr,wasHurt]:hitEnemies){ if(!std::holds_alternative(targetPtr))ERR("WARNING! A hurt request list was expecting only monster pointers, but got another type instead! THIS SHOULD NOT BE HAPPENING!"); @@ -798,37 +798,55 @@ bool Player::HasIframes(){ return iframe_time>0; } -bool Player::Hurt(int damage,bool onUpperLevel,float z){ - if(!IsAlive()||HasIframes()||OnUpperLevel()!=onUpperLevel||abs(GetZ()-z)>1) return false; +bool Player::Hurt(HurtDamageInfo damageData){ + return Hurt(damageData.damage,damageData.onUpperLevel,damageData.z,damageData.damageRule,damageData.hurtFlags); +} + +bool Player::Hurt(int damage,bool onUpperLevel,float z,HurtFlag::HurtFlag hurtFlags){ + return Hurt(damage,onUpperLevel,z,TrueDamageFlag::NORMAL_DAMAGE,hurtFlags); +} + +bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule,HurtFlag::HurtFlag hurtFlags){ + const bool IsDOT{bool(hurtFlags&HurtFlag::DOT)}; + const bool TrueDamage{damageRule==TrueDamageFlag::IGNORE_DAMAGE_RULES}; + + const bool NormalDamageCalculationRequired{!IsDOT&&!TrueDamage}; + const bool PlayHitSoundEffect{!IsDOT}; + + if(!TrueDamage&&(!IsAlive()||HasIframes()||OnUpperLevel()!=onUpperLevel||abs(GetZ()-z)>1))return false; float mod_dmg=float(damage); - if(GetState()==State::BLOCK){ - mod_dmg=0; - SoundEffect::PlaySFX("Warrior Block Hit",SoundEffect::CENTERED); - }else{ - float otherDmgTaken=1-GetDamageReductionFromBuffs(); - float armorDmgTaken=1-GetDamageReductionFromArmor(); - lastCombatTime=0; + if(NormalDamageCalculationRequired){ + if(GetState()==State::BLOCK){ + mod_dmg=0; + SoundEffect::PlaySFX("Warrior Block Hit",SoundEffect::CENTERED); + }else{ + float otherDmgTaken=1-GetDamageReductionFromBuffs(); + float armorDmgTaken=1-GetDamageReductionFromArmor(); + lastCombatTime=0; - float finalPctDmgTaken=armorDmgTaken*otherDmgTaken; + float finalPctDmgTaken=armorDmgTaken*otherDmgTaken; - if(finalPctDmgTaken<=6._Pct){ - LOG("WARNING! Damage Reduction has somehow ended up below 6%, which is over the cap!"); - } + if(finalPctDmgTaken<=6._Pct){ + LOG("WARNING! Damage Reduction has somehow ended up below 6%, which is over the cap!"); + } - finalPctDmgTaken=std::max(6.25_Pct,finalPctDmgTaken);//Apply Damage Cap. + finalPctDmgTaken=std::max(6.25_Pct,finalPctDmgTaken);//Apply Damage Cap. - float minPctDmgReduction=0.05_Pct*GetDefense(); - float finalPctDmgReduction=1-finalPctDmgTaken; + float minPctDmgReduction=0.05_Pct*GetDefense(); + float finalPctDmgReduction=1-finalPctDmgTaken; - float pctDmgReductionDiff=finalPctDmgReduction-minPctDmgReduction; - float dmgRoll=minPctDmgReduction+util::random(pctDmgReductionDiff); + float pctDmgReductionDiff=finalPctDmgReduction-minPctDmgReduction; + float dmgRoll=minPctDmgReduction+util::random(pctDmgReductionDiff); - mod_dmg*=1-dmgRoll; + mod_dmg*=1-dmgRoll; + } + } - mod_dmg=std::ceil(mod_dmg); + mod_dmg=std::ceil(mod_dmg); + + if(PlayHitSoundEffect)SoundEffect::PlaySFX("Player Hit",SoundEffect::CENTERED); + - SoundEffect::PlaySFX("Player Hit",SoundEffect::CENTERED); - } if(Menu::IsMenuOpen()&&mod_dmg>0)Menu::CloseAllMenus(); if(mod_dmg>0)game->ShowDamageVignetteOverlay(); @@ -840,7 +858,17 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z){ hurtRumbleTime="Player.Hurt Rumble Time"_F; Input::StartVibration(); Input::SetLightbar(PixelLerp(DARK_RED,GREEN,GetHealthRatio())); - + + if(IsDOT){ + if(lastDotTimer>0){ + dotNumberPtr.get()->damage+=int(mod_dmg); + dotNumberPtr.get()->RecalculateSize(); + }else{ + dotNumberPtr=std::make_shared(pos-vf2d{0,8.f},int(mod_dmg),false,DamageNumberType::DOT); + DAMAGENUMBER_LIST.push_back(dotNumberPtr); + } + lastDotTimer=0.05f; + }else if(lastHitTimer>0){ damageNumberPtr.get()->damage+=int(mod_dmg); damageNumberPtr.get()->pauseTime=0.4f; @@ -895,7 +923,7 @@ void Player::CancelCast(){ castInfo={"",0}; std::erase_if(buffList,[](Buff&b){return b.type==RESTORATION_DURING_CAST;}); //Remove all buffs that would be applied during a cast, as we got interrupted. if(wasCasting){ - DAMAGENUMBER_LIST.push_back(std::make_shared(GetPos(),0,true,INTERRUPT)); + DAMAGENUMBER_LIST.push_back(std::make_shared(GetPos(),0,true,DamageNumberType::INTERRUPT)); } if(state==State::CASTING){ state=State::NORMAL; @@ -1115,7 +1143,7 @@ void Player::_SetIframes(float duration){ bool Player::Heal(int damage,bool suppressDamageNumber){ hp=std::clamp(hp+damage,0,int(GetMaxHealth())); if(!suppressDamageNumber&&damage>0){ - DAMAGENUMBER_LIST.push_back(std::make_shared(GetPos(),damage,true,HEALTH_GAIN)); + DAMAGENUMBER_LIST.push_back(std::make_shared(GetPos(),damage,true,DamageNumberType::HEALTH_GAIN)); } Input::SetLightbar(PixelLerp(DARK_RED,GREEN,GetHealthRatio())); return true; @@ -1124,7 +1152,7 @@ bool Player::Heal(int damage,bool suppressDamageNumber){ void Player::RestoreMana(int amt,bool suppressDamageNumber){ mana=std::clamp(mana+amt,0,GetMaxMana()); if(amt>0&&!suppressDamageNumber){ - DAMAGENUMBER_LIST.push_back(std::make_shared(GetPos(),amt,true,MANA_GAIN)); + DAMAGENUMBER_LIST.push_back(std::make_shared(GetPos(),amt,true,DamageNumberType::MANA_GAIN)); } } diff --git a/Adventures in Lestoria/Player.h b/Adventures in Lestoria/Player.h index e419ec08..041ba2a9 100644 --- a/Adventures in Lestoria/Player.h +++ b/Adventures in Lestoria/Player.h @@ -193,8 +193,10 @@ public: void UpdateHealthAndMana(); void RecalculateEquipStats(); - - bool Hurt(int damage,bool onUpperLevel,float z); + + bool Hurt(HurtDamageInfo damageData); + bool Hurt(int damage,bool onUpperLevel,float z,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE); + bool Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE); //Return false if healing was not possible. bool Heal(int damage,bool suppressDamageNumber=false); //specificClass is a bitwise-combination of classes from the Class enum. It makes sure certain animations only play if you are a certain class.= @@ -324,7 +326,8 @@ private: CastInfo castInfo={"",0}; vf2d movementVelocity={};//This tells us if the player is moving (mostly controlled by user input) since their velocity is not used for regular movement. float lastHitTimer=0; //When this is greater than zero, if we get hit again it adds to our displayed combo number. - std::shared_ptrdamageNumberPtr; + float lastDotTimer=0; //When this is greater than zero, if we get hit again it adds to our displayed combo number. + std::shared_ptrdamageNumberPtr,dotNumberPtr; void Initialize(); float iframe_time=0; float lastCombatTime=0; diff --git a/Adventures in Lestoria/SwordSlash.cpp b/Adventures in Lestoria/SwordSlash.cpp index dd5e7c6a..f7ad0244 100644 --- a/Adventures in Lestoria/SwordSlash.cpp +++ b/Adventures in Lestoria/SwordSlash.cpp @@ -46,7 +46,7 @@ SwordSlash::SwordSlash(float lifetime, std::string imgFile,float damageMult, flo bool SwordSlash::Update(float fElapsedTime){ if(lifetime>0){ - game->HurtConeNotHit(game->GetPlayer()->GetPos(),game->GetPlayer()->GetAttackRangeMult()*12.f,rotation,util::degToRad(swordSweepAngle),game->GetPlayer()->GetAttack()*damageMult,hitList,game->GetPlayer()->OnUpperLevel(),game->GetPlayer()->GetZ(),HurtType::MONSTER); + game->HurtConeNotHit(game->GetPlayer()->GetPos(),game->GetPlayer()->GetAttackRangeMult()*12.f,rotation,util::degToRad(swordSweepAngle),game->GetPlayer()->GetAttack()*damageMult,hitList,game->GetPlayer()->OnUpperLevel(),game->GetPlayer()->GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY); } pos=game->GetPlayer()->GetPos(); diff --git a/Adventures in Lestoria/Thief.cpp b/Adventures in Lestoria/Thief.cpp index 2be1c81b..b9558ad3 100644 --- a/Adventures in Lestoria/Thief.cpp +++ b/Adventures in Lestoria/Thief.cpp @@ -156,7 +156,7 @@ void Thief::InitializeClassAbilities(){ #pragma region Thief Ability 3 (Adrenaline Rush) Thief::ability3.action= [](Player*p,vf2d pos={}){ - SoundEffect::PlaySFX("Adrenaline Rush",p->GetPos()); + SoundEffect::PlaySFX("Adrenaline Rush",SoundEffect::CENTERED); p->AddBuff(BuffType::ADRENALINE_RUSH,"Thief.Ability 3.Duration"_F,0.f); for(int i:std::ranges::iota_view(0,50)){ float size{util::random_range(0.4f,0.8f)}; diff --git a/Adventures in Lestoria/Tornado.cpp b/Adventures in Lestoria/Tornado.cpp index 8c5d86cf..b91b5c65 100644 --- a/Adventures in Lestoria/Tornado.cpp +++ b/Adventures in Lestoria/Tornado.cpp @@ -75,4 +75,6 @@ BulletDestroyState Tornado::MonsterHit(Monster&monster){ monster.ApplyIframes(knockupDuration*2); return BulletDestroyState::KEEP_ALIVE; -} \ No newline at end of file +} + +void Tornado::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/Trapper.cpp b/Adventures in Lestoria/Trapper.cpp index 5c94c6bf..ad5e576b 100644 --- a/Adventures in Lestoria/Trapper.cpp +++ b/Adventures in Lestoria/Trapper.cpp @@ -41,6 +41,10 @@ All rights reserved. #include "Effect.h" #include "AdventuresInLestoria.h" #include "config.h" +#include "SoundEffect.h" +#include "BulletTypes.h" +#include +#include "util.h" INCLUDE_MONSTER_LIST INCLUDE_BULLET_LIST @@ -65,19 +69,35 @@ void Trapper::OnUpdate(float fElapsedTime){ } bool Trapper::AutoAttack(){ - return false; + geom2d::line pointTowardsCursor(GetPos(),GetWorldAimingLocation()); + vf2d extendedLine=pointTowardsCursor.upoint(1.1f); + float angleToCursor=atan2(extendedLine.y-GetPos().y,extendedLine.x-GetPos().x); + attack_cooldown_timer=ARROW_ATTACK_COOLDOWN-GetAttackRecoveryRateReduction(); + CreateBullet(Arrow)(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F-PI/8*"Ranger.Auto Attack.ArrowSpd"_F)}+movementVelocity/1.5f,"Ranger.Auto Attack.Radius"_F,int(GetAttack()*"Ranger.Auto Attack.DamageMult"_F),OnUpperLevel(),true)EndBullet; + BULLET_LIST.back()->SetIsPlayerAutoAttackProjectile(); + SetState(State::SHOOT_ARROW); + SetAnimationBasedOnTargetingDirection("SHOOT",angleToCursor); + SoundEffect::PlaySFX("Ranger.Auto Attack.Sound"_S,SoundEffect::CENTERED); + return true; } void Trapper::InitializeClassAbilities(){ - #pragma region Trapper Right-click Ability (???) + #pragma region Trapper Right-click Ability (Sprint) Trapper::rightClickAbility.action= [](Player*p,vf2d pos={}){ - return false; + SoundEffect::PlaySFX("Adrenaline Rush",SoundEffect::CENTERED); + p->AddBuff(BuffType::SPEEDBOOST,"Trapper.Right Click Ability.Movement Speed Buff"_f[1],"Trapper.Right Click Ability.Movement Speed Buff"_f[0]/100.f); + for(int i:std::ranges::iota_view(0,50)){ + float size{util::random_range(0.4f,0.8f)}; + game->AddEffect(std::make_unique(p->GetPos()+vf2d{8,util::random(2*PI)}.cart(),util::random_range(0.1f,0.4f),"circle.png",p->OnUpperLevel(),vf2d{size,size},0.3f,vf2d{util::random_range(-6.f,6.f),util::random_range(-8.f,-1.f)},PixelLerp(BLACK,RED,util::random(1)))); + } + return true; }; #pragma endregion - #pragma region Trapper Ability 1 (???) + #pragma region Trapper Ability 1 (Mark Target) Trapper::ability1.action= [](Player*p,vf2d pos={}){ - return false; + + return true; }; #pragma endregion #pragma region Trapper Ability 2 (???) diff --git a/Adventures in Lestoria/Ursule.cpp b/Adventures in Lestoria/Ursule.cpp index 6ae631fd..14b64d00 100644 --- a/Adventures in Lestoria/Ursule.cpp +++ b/Adventures in Lestoria/Ursule.cpp @@ -62,7 +62,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy #pragma region Setup On Death Function m.SetStrategyDeathFunction([&](GameEvent&event,Monster&m,const std::string&strategy){ if(!m.B(A::BULLETS_REMOVED)){ - for(const std::unique_ptr&b:BULLET_LIST){ + for(const std::unique_ptr&b:BULLET_LIST){ b->fadeOutTime=ConfigFloat("Phase 4.End Wisp Fadeout Time"); } m.B(A::BULLETS_REMOVED)=true; diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 35533550..a29a3e7f 100644 --- a/Adventures in Lestoria/Version.h +++ b/Adventures in Lestoria/Version.h @@ -39,7 +39,7 @@ All rights reserved. #define VERSION_MAJOR 1 #define VERSION_MINOR 2 #define VERSION_PATCH 3 -#define VERSION_BUILD 10219 +#define VERSION_BUILD 10248 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/Wisp.cpp b/Adventures in Lestoria/Wisp.cpp index 4ab1a556..0b8d47be 100644 --- a/Adventures in Lestoria/Wisp.cpp +++ b/Adventures in Lestoria/Wisp.cpp @@ -59,4 +59,6 @@ BulletDestroyState Wisp::MonsterHit(Monster&monster){ game->AddEffect(std::make_unique(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),0.25,vf2d{},"MonsterStrategy.Ursule.Phase 2.Wisp Color"_Pixel)); fadeOutTime="MonsterStrategy.Ursule.Phase 2.Wisp Fadeout Time"_F; return BulletDestroyState::KEEP_ALIVE; -} \ No newline at end of file +} + +void Wisp::ModifyOutgoingDamageData(int&damage,bool&onUpperLevel,float&z,HurtFlag::HurtFlag&hurtFlags){} \ No newline at end of file diff --git a/Adventures in Lestoria/Zephy.cpp b/Adventures in Lestoria/Zephy.cpp index a252af8f..5b7c6032 100644 --- a/Adventures in Lestoria/Zephy.cpp +++ b/Adventures in Lestoria/Zephy.cpp @@ -96,7 +96,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy) game->SetWindSpeed({}); game->GetOverlay().Disable(); - std::for_each(BULLET_LIST.begin(),BULLET_LIST.end(),[](const std::unique_ptr&bullet){ + std::for_each(BULLET_LIST.begin(),BULLET_LIST.end(),[](const std::unique_ptr&bullet){ if(!bullet->friendly){ //Forces all bullets at the end of a fight for the boss to be completely nullified. bullet->fadeOutTime=0.5f; } @@ -182,8 +182,8 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy) RUN_TOWARDS(m,fElapsedTime,"Run Towards"); m.F(A::SHOOT_TIMER)-=fElapsedTime; if(m.F(A::SHOOT_TIMER)<=0.f){ - CreateBullet(Bullet)(m.GetPos(),vf2d{0.f,ConfigFloat("Fly Across Attack.Attack Y Speed")},4,ConfigInt("Fly Across Attack.Poop Damage"),"birdpoop.png",m.OnUpperLevel(),false,INFINITY,false,false,WHITE,vf2d{1.f,1.25f}) - .SetIframeTimeOnHit(0.25f)EndBullet; + CreateBullet(Bullet)(m.GetPos(),vf2d{0.f,ConfigFloat("Fly Across Attack.Attack Y Speed")},4,ConfigInt("Fly Across Attack.Poop Damage"),"birdpoop.png",m.OnUpperLevel(),false,INFINITY,false,false,WHITE,vf2d{1.f,1.25f})EndBullet; + BULLET_LIST.back()->SetIframeTimeOnHit(0.25f); const int extraPoopBitsCount=util::random()%6; for(int i=0;iSetIframeTimeOnHit(0.25f); } m.F(A::SHOOT_TIMER)=ConfigFloat("Fly Across Attack.Attack Frequency"); } @@ -349,7 +349,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy) m.PerformAnimation("ATTACK"); if(game->BossEncounterMobCount()==1){ m.phase=IDLE; - std::for_each(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr&bullet){ + std::for_each(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr&bullet){ if(bullet->GetBulletType()==BulletType::LARGE_TORNADO){ bullet->fadeOutTime=1.f; }else if(bullet->GetBulletType()==BulletType::FEATHER){ diff --git a/Adventures in Lestoria/assets/config/classes/Trapper.txt b/Adventures in Lestoria/assets/config/classes/Trapper.txt index e61f91af..576d7802 100644 --- a/Adventures in Lestoria/assets/config/classes/Trapper.txt +++ b/Adventures in Lestoria/assets/config/classes/Trapper.txt @@ -16,7 +16,7 @@ Trapper { DamageMult = 1x Radius = 12 - Cooldown = 0.6 + Cooldown = 0.6s # Whether or not this ability cancels casts. CancelCast = 0 @@ -31,7 +31,7 @@ Trapper Short Name = SPRINT Description = Gain 60% bonus movement speed for 3 seconds. Icon = sprint.png - Cooldown = 7 + Cooldown = 7s Mana Cost = 0 # Whether or not this ability cancels casts. CancelCast = 0 diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index 130f1d25..db738ba6 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ