Finished converting Battle Cry to Entity compatibility. Add unit test to ensure block works for monsters.
All checks were successful
Emscripten Build / Build_and_Deploy_Web_Build (push) Successful in 13m57s
Emscripten Build / UnitTesting (push) Successful in 8m21s

This commit is contained in:
AMay 2026-05-04 22:40:14 -05:00
parent 81774b55f0
commit 00704856b0
12 changed files with 91 additions and 36 deletions

View File

@ -1094,30 +1094,39 @@
<ClCompile Include="Spider.cpp" />
<ClCompile Include="tests\BuffTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\EffectTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\EnchantTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\EngineTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\FileTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\GeometryTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\ItemTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\MonsterTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="tests\PlayerTests.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="ThunderOrb.cpp" />
<ClCompile Include="TileGroup.cpp" />

View File

@ -69,10 +69,11 @@ namespace classutils{//Classes have bit-wise operator capabilities.
}
};
#define CONFIG_F(var)e.IsFriendly() ? var##_F : Monster::STRATEGY::_GetFloat(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName())
#define CONFIG_I(var)e.IsFriendly() ? var##_I : Monster::STRATEGY::_GetInt(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName())
#define CONFIG_S(var)e.IsFriendly() ? var##_S : Monster::STRATEGY::_GetString(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName())
#define CONFIG_F(var)(e.IsFriendly() ? var##_F : Monster::STRATEGY::_GetFloat(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName()))
#define CONFIG_f(var,index)(e.IsFriendly() ? var##_f[index] : Monster::STRATEGY::_GetFloat(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName(),index))
#define CONFIG_I(var)(e.IsFriendly() ? var##_I : Monster::STRATEGY::_GetInt(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName()))
#define CONFIG_S(var)(e.IsFriendly() ? var##_S : Monster::STRATEGY::_GetString(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName()))
#define IFPLAYER e.ForPlayer([](Player&p){
#define IFMONSTER e.ForMonster([](Monster&m){
#define IFPLAYER e.ForPlayer([&](Player&p){
#define IFMONSTER e.ForMonster([&](Monster&m){
#define ENDIF });

View File

@ -144,9 +144,17 @@ Buff&Entity::AddBuff(BuffType type,float duration,float intensity){
CallClassFunc(AddBuff(type,duration,intensity));
}
Buff&Entity::AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr){
CallClassFunc(AddBuff(type,duration,intensity,attr));
}
void Entity::ForPlayer(std::function<void(Player&p)>func){
if(is(Player*))func(*get(Player*));
}
void Entity::ForMonster(std::function<void(Monster&p)>func){
if(is(Monster*))func(*get(Monster*));
}
const float Entity::GetZ()const{
CallClassFunc(GetZ());
}

View File

@ -75,12 +75,14 @@ public:
const float GetMoveSpdMult()const;
const float GetSizeMult()const;
const int GetAttack()const;
const float GetZ()const;
const FriendlyType IsFriendly()const;
const State::State GetState()const;
const bool CanMove()const;
const bool HasEnchant(const std::string&enchant)const;
float&GetBlockTimer()const;
Buff&AddBuff(BuffType type,float duration,float intensity);
Buff&AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr);
private:
mutableconst std::variant<Monster*,Player*>entity;
inline bool operator==(const Entity&rhs){return entity==rhs.entity;}

View File

@ -573,10 +573,10 @@ void Monster::Draw()const{
const auto DrawBaseMonster=[&](vf2d scale={1.f,1.f},Pixel col=WHITE){
game->view.DrawPartialRotatedDecal(drawPos,GetFrame().GetSourceImage()->Decal(),finalSpriteRot,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,col);
if(blockTimer>0.f)game->view.DrawPartialRotatedDecal(drawPos,GFX["block.png"].Decal(),finalSpriteRot,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,col);
};
const auto DrawOverlayMonster=[&](vf2d scale={1.f,1.f},Pixel col=WHITE){
game->view.DrawPartialRotatedDecal(drawPos,GFX[overlaySprite].Decal(),finalSpriteRot,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,col);
if(blockTimer>0.f)game->view.DrawPartialRotatedDecal(drawPos,GFX["block.png"].Decal(),finalSpriteRot,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,col);
};
const auto DrawMountedMonster=[&](vf2d scale={1.f,1.f},Pixel col=WHITE){
game->view.DrawPartialRotatedDecal(drawPos+mountedSprOffset,GetMountedFrame().value().GetSourceImage()->Decal(),finalSpriteRot,GetMountedFrame().value().GetSourceRect().size/2,GetMountedFrame().value().GetSourceRect().pos,GetMountedFrame().value().GetSourceRect().size,scale,col);
@ -822,6 +822,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
#pragma endregion
mod_dmg-=mod_dmg*GetDamageReductionFromBuffs();
if(blockTimer)mod_dmg=0;
}
if(HitByPlayerAbility){
@ -897,9 +898,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
SoundEffect::PlaySFX(GetHurtSound(),GetPos());
}
}
if(game->InBossEncounter()&&isBoss){
game->BossDamageDealt(int(mod_dmg));
}
if(game->InBossEncounter()&&isBoss)game->BossDamageDealt(int(mod_dmg));
using A=Attribute;
GetInt(A::HITS_UNTIL_DEATH)=std::max(0,GetInt(A::HITS_UNTIL_DEATH)-1);

View File

@ -485,13 +485,6 @@ private:
vf2d addedVel{};
std::weak_ptr<Monster>attachedTarget; //A monster attached to another monster can then use this to alter behaviors based on the state of that other monster.
float unconsciousTimer{};
#ifdef UNIT_TESTING
public:
#endif
const bool IsUnconscious()const;
#ifdef UNIT_TESTING
private:
#endif
const float UnconsciousTime()const;
bool manualIgnoreTerrain{false}; //A manual flag that can be toggled on to dynamically make this monster ignore terrain collision.
float collisionRadius{}; //The collision radius can be modified, it's just set initially to the monster database entry.
@ -511,7 +504,14 @@ private:
bool bumpedIntoTerrain=false; //Gets set to true before a strategy executes if the monster runs into some terrain on this frame.
bool attackedByPlayer=false; //Gets set to true before a strategy executes if the monster has been attacked by the player.
uint8_t transparency{255U};
#ifdef UNIT_TESTING
public:
#endif
const bool IsUnconscious()const;
float blockTimer{};
#ifdef UNIT_TESTING
private:
#endif
};
struct MonsterSpawner{

View File

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 3
#define VERSION_PATCH 0
#define VERSION_BUILD 13532
#define VERSION_BUILD 13551
#define stringify(a) stringify_(a)
#define stringify_(a) #a

View File

@ -57,7 +57,7 @@ DEFINE_STRATEGY(WARRIORTHIEF)
const float distToPlayer{util::distance(m.GetPos(),game->GetPlayer()->GetPos())};
if(distToPlayer<100.f&&m.F(A::DEFENSIVE_COOLDOWN)<=0.f){
m.F(A::DEFENSIVE_COOLDOWN)=ConfigFloat("Warrior.Right Click Ability.Cooldown");
Warrior::rightClickAbility.action(&m,m.GetPos());
Warrior::ability1.action(&m,m.GetPos());
}
}break;
}

View File

@ -42,6 +42,7 @@ All rights reserved.
#include "AdventuresInLestoria.h"
#include "config.h"
#include "SoundEffect.h"
#include<ranges>
INCLUDE_MONSTER_LIST
INCLUDE_BULLET_LIST
@ -125,17 +126,17 @@ void Warrior::InitializeClassAbilities(){
#pragma endregion
#pragma region Warrior Ability 1 (Battlecry)
Warrior::ability1.action=
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
game->AddEffect(Effect{p->GetPos(),CONFIG_F("Warrior.Ability 1.EffectLifetime"),"battlecry_effect.png",p->upperLevel,"Warrior.Ability 1.Range"_F/350,"Warrior.Ability 1.EffectFadetime"_F});
p->AddBuff(BuffType::STAT_UP,"Warrior.Ability 1.AttackUpDuration"_F,"Warrior.Ability 1.AttackIncrease"_F,{"Attack %"});
p->AddBuff(BuffType::DAMAGE_REDUCTION,"Warrior.Ability 1.DamageReductionDuration"_F,"Warrior.Ability 1.DamageReduction"_F);
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(m->GetSizeMult()>="Warrior.Ability 1.AffectedSizeRange"_f[0]&&m->GetSizeMult()<="Warrior.Ability 1.AffectedSizeRange"_f[1]&&geom2d::overlaps(geom2d::circle<float>(p->GetPos(),12*"Warrior.Ability 1.Range"_I/100.f),geom2d::circle<float>(m->GetPos(),m->GetSizeMult()*12))){
m->AddBuff(BuffType::SLOWDOWN,"Warrior.Ability 1.SlowdownDuration"_F,"Warrior.Ability 1.SlowdownAmt"_F);
if(p->HasEnchant("Battle Shout"))m->Hurt(p->GetDefense()*"Battle Shout"_ENC["DEFENSE DAMAGE"]/100.f,p->OnUpperLevel(),p->GetZ());
}
[](Entity e,vf2d pos={}){
game->AddEffect(Effect{e.GetPos(),CONFIG_F("Warrior.Ability 1.EffectLifetime"),"battlecry_effect.png",e.OnUpperLevel(),CONFIG_F("Warrior.Ability 1.Range")/350,CONFIG_F("Warrior.Ability 1.EffectFadetime")});
e.AddBuff(BuffType::STAT_UP,CONFIG_F("Warrior.Ability 1.AttackUpDuration"),CONFIG_F("Warrior.Ability 1.AttackIncrease"),{"Attack %"});
e.AddBuff(BuffType::DAMAGE_REDUCTION,CONFIG_F("Warrior.Ability 1.DamageReductionDuration"),CONFIG_F("Warrior.Ability 1.DamageReduction"));
for(Entity&affectedEntity:game->GetTargetsInRange(e.GetPos(),12*CONFIG_I("Warrior.Ability 1.Range")/100.f,e.OnUpperLevel(),e.GetZ(),e.IsFriendly()?HurtType::MONSTER:HurtType::PLAYER)|std::views::filter([&e](Entity&filteredEnt){
return filteredEnt.GetSizeMult()>=CONFIG_f("Warrior.Ability 1.AffectedSizeRange",0)&&filteredEnt.GetSizeMult()<=CONFIG_f("Warrior.Ability 1.AffectedSizeRange",1);})){
affectedEntity.AddBuff(BuffType::SLOWDOWN,CONFIG_F("Warrior.Ability 1.SlowdownDuration"),CONFIG_F("Warrior.Ability 1.SlowdownAmt"));
IFPLAYER{if(p.HasEnchant("Battle Shout"))affectedEntity.Hurt(p.GetDefense()*"Battle Shout"_ENC["DEFENSE DAMAGE"]/100.f,p.OnUpperLevel(),p.GetZ());}ENDIF
}
SoundEffect::PlaySFX("Warrior Battlecry",SoundEffect::CENTERED);
SoundEffect::PlaySFX("Warrior Battlecry",e.GetPos());
return true;
};
#pragma endregion

View File

@ -1481,5 +1481,34 @@ MonsterStrategy
Warrior.Right Click Ability.Duration = 3s
Warrior.Right Click Ability.SlowAmt = 0.3
Warrior.Right Click Ability.Cooldown = 15s
# Amount of time in seconds the attack damage increase lasts.
Warrior.Ability 1.AttackUpDuration = 10
# Percentage of attack damage to increase by.
Warrior.Ability 1.AttackIncrease = 0.1
# Amount of time in seconds the damage reduction increase lasts.
Warrior.Ability 1.DamageReductionDuration = 10
# Percentage of damage reduction to increase by.
Warrior.Ability 1.DamageReduction = 0.1
# The smallest and largest size of enemies this ability affects (inclusive).
Warrior.Ability 1.AffectedSizeRange = 0,1
# How long the applied slow debuff lasts in seconds.
Warrior.Ability 1.SlowdownDuration = 6
# Percentage of speed down applied to affected enemies.
Warrior.Ability 1.SlowdownAmt = 0.4
# Amount of time the effect lives for on-screen before fading begins.
Warrior.Ability 1.EffectLifetime = 0.1
# Amount of time the effect fades out.
Warrior.Ability 1.EffectFadetime = 0.3
Warrior.Ability 1.Cooldown = 12
Warrior.Ability 1.Range = 500
}
}

View File

@ -3,15 +3,15 @@ HUB = 2026-02-27 21:53:33.6533357
TEST_MAP = 2026-01-26 20:40:12.7201277
BOSS_1 = 2026-02-27 21:53:33.6513354
WORLD_MAP = 2026-02-27 21:53:33.6568368
CAMPAIGN_1_1 = 2026-01-21 20:30:58.3723266
CAMPAIGN_1_1 = 2026-05-04 03:43:46.2305986
BOSS_1_B = 2024-09-13 21:28:58.8886132
BOSS_2 = 2026-01-21 20:30:58.4312572
BOSS_3 = 2026-04-23 20:04:39.8919861
BOSS_3_B = 2026-04-23 20:04:39.8919861
BOSS_3_B = 2026-05-04 20:46:34.9920916
CAMPAIGN_7_1 = 2024-09-13 21:28:58.8570782
CAMPAIGN_1_4 = 2026-04-28 20:00:03.4806604
CAMPAIGN_1_4 = 2026-05-04 20:46:34.9852665
BOSS_2_B = 2026-01-21 20:30:58.4322614
CAMPAIGN_1_2 = 2026-04-28 19:44:26.2017819
CAMPAIGN_1_2 = 2026-05-04 20:46:34.9832592
CAMPAIGN_1_3 = 2024-09-13 21:28:58.8532684
CAMPAIGN_1_5 = 2024-09-13 21:28:58.8557772
CAMPAIGN_4_8 = 2026-02-26 21:44:13.5419545
@ -21,10 +21,10 @@ CAMPAIGN_1_B1 = 2024-09-13 21:28:58.8605592
CAMPAIGN_1_8 = 2024-09-13 21:28:58.8595553
CAMPAIGN_4_5 = 2026-02-26 21:44:13.5315825
CAMPAIGN_2_1 = 2026-01-21 20:30:58.3756334
CAMPAIGN_2_2 = 2026-04-28 19:44:26.2126272
CAMPAIGN_2_3 = 2026-04-23 20:04:39.8896736
CAMPAIGN_2_2 = 2026-05-04 20:46:34.9887816
CAMPAIGN_2_3 = 2026-04-29 02:06:13.0636912
CAMPAIGN_2_4 = 2026-01-21 20:30:58.3804324
CAMPAIGN_2_5 = 2026-04-23 20:04:39.8906724
CAMPAIGN_2_5 = 2026-05-04 20:46:34.9911006
CAMPAIGN_2_6 = 2026-01-21 20:30:58.3836104
CAMPAIGN_2_7 = 2026-01-21 20:30:58.3846135
CAMPAIGN_2_8 = 2026-01-21 20:30:58.3866133
@ -38,7 +38,7 @@ CAMPAIGN_3_5 = 2026-01-21 20:30:58.3977329
CAMPAIGN_3_6 = 2026-01-21 20:30:58.4008008
CAMPAIGN_3_7 = 2026-01-21 20:30:58.4023071
CAMPAIGN_3_8 = 2026-01-21 20:30:58.4049027
CAMPAIGN_3_B1 = 2026-04-23 20:04:39.8919861
CAMPAIGN_3_B1 = 2026-05-04 20:46:34.9920916
CAMPAIGN_4_1 = 2026-04-13 18:08:48.5353335
CAMPAIGN_4_2 = 2026-02-26 21:44:13.5210572
CAMPAIGN_4_4 = 2026-02-26 21:44:13.5270745

View File

@ -636,3 +636,9 @@ TEST(MonsterTests,"MonsterManaRecoveryTest"){
REQUIRE(testMonster.mp==1);
REQUIRE(testMonster.mpRemainder==0.f);
}
TEST(MonsterTests,"MonsterBlockingTest"){
Monster&fireMage{game->SpawnMonster({24,24},MONSTER_DATA.at("Skeleton Fire Mage"))};
fireMage.blockTimer=5.f;
fireMage.Hurt(100,fireMage.OnUpperLevel(),fireMage.GetZ());
REQUIRE(fireMage.GetMaxHealth()==fireMage.GetHealth());
}