Remove unnecessary variables from old monster update code, remove return value, create unnecessary ambiguity with why we would need to return true/false for no purpose.

Add Unconscious timer and tests for Monsters.
master
sigonasr2 2 months ago
parent 970b15ac5d
commit 3024111b4a
  1. 18
      Adventures in Lestoria Tests/MonsterTests.cpp
  2. 54
      Adventures in Lestoria/Monster.cpp
  3. 5
      Adventures in Lestoria/Monster.h
  4. 5
      Adventures in Lestoria/MonsterData.cpp
  5. 2
      Adventures in Lestoria/MonsterData.h
  6. 4
      Adventures in Lestoria/Parrot.cpp
  7. 1
      Adventures in Lestoria/Pirate_Captain.cpp

@ -506,5 +506,23 @@ namespace MonsterTests
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
}
TEST_METHOD(UnconsciousMonsterTest){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
Assert::IsFalse(parrot.IsUnconscious(),L"Parrot should not be immediately unconscious.");
parrot.Hurt(1,parrot.OnUpperLevel(),parrot.GetZ());
Assert::IsFalse(parrot.IsUnconscious(),L"Parrot should not be unconscious after taking 1 damage.");
parrot.Hurt(parrot.GetMaxHealth(),parrot.OnUpperLevel(),parrot.GetZ());
Assert::IsTrue(parrot.IsUnconscious(),L"Parrot should now be unconscious.");
Assert::IsTrue(parrot.IsDead(),L"Parrot is considered dead.");
Assert::IsTrue(parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()),L"Parrot should be in an undamageable state. Hopefully for obvious reasons.");
game->OnUserUpdate(MONSTER_DATA.at("Parrot").UnconsciousTime()/2);
Assert::IsTrue(parrot.IsUnconscious(),L"Parrot should still be unconscious.");
Assert::IsTrue(parrot.IsDead(),L"Parrot should still be considered dead.");
Assert::IsTrue(parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()),L"Parrot should still be in an undamageable state.");
game->OnUserUpdate(MONSTER_DATA.at("Parrot").UnconsciousTime()/2);
Assert::IsTrue(!parrot.IsUnconscious(),L"Parrot should no longer be unconscious.");
Assert::IsTrue(parrot.IsAlive(),L"Parrot should be alive.");
Assert::IsTrue(!parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()),L"Parrot should not be in an undamageable state.");
}
};
}

@ -267,7 +267,7 @@ bool Monster::SetY(float y){
return _SetY(y);
}
bool Monster::Update(float fElapsedTime){
void Monster::Update(const float fElapsedTime){
lastHitTimer=std::max(0.f,lastHitTimer-fElapsedTime);
lastDotTimer=std::max(0.f,lastDotTimer-fElapsedTime);
iframe_timer=std::max(0.f,iframe_timer-fElapsedTime);
@ -280,6 +280,14 @@ bool Monster::Update(float fElapsedTime){
lastFacingDirectionChange+=fElapsedTime;
timeSpentAlive+=fElapsedTime;
if(IsUnconscious()){
unconsciousTimer=std::max(0.f,unconsciousTimer-fElapsedTime);
if(unconsciousTimer==0.f){
Heal(GetMaxHealth());
}
}
if(IsSolid()&&FadeoutWhenStandingBehind()){
if(GetPos().y>=game->GetPlayer()->GetPos().y)solidFadeTimer=std::min(TileGroup::FADE_TIME,solidFadeTimer+game->GetElapsedTime());
else solidFadeTimer=std::max(0.f,solidFadeTimer-game->GetElapsedTime());
@ -394,17 +402,9 @@ bool Monster::Update(float fElapsedTime){
}
if(!game->TestingModeEnabled()&&CanMove())Monster::STRATEGY::RUN_STRATEGY(*this,fElapsedTime);
}
if(!IsAlive()){
deathTimer+=fElapsedTime;
if(deathTimer>3){
return false;
}
}
animation.UpdateState(internal_animState,randomFrameOffset+fElapsedTime);
if(HasMountedMonster())mounted_animation.value().UpdateState(internal_mounted_animState,fElapsedTime);
randomFrameOffset=0;
attackedByPlayer=false;
return true;
}
Direction Monster::GetFacingDirection()const{
return facingDirection;
@ -636,6 +636,8 @@ void Monster::DrawReflection(float drawRatioX,float multiplierX){
};
}
if(IsUnconscious()&&animation.HasState(MONSTER_DATA.at(name).GetDefaultDeathAnimation()))animation.ChangeState(internal_animState,GetDeathAnimationName());
game->view.DrawPartialWarpedDecal(GetFrame().GetSourceImage()->Decal(),points,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size);
game->SetDecalMode(DecalMode::NORMAL);
}
@ -1144,21 +1146,21 @@ void Monster::OnDeath(){
}
if(game->GetPlayer()->HasEnchant("Curse of Doom")&&accumulatedCurseOfDeathDamage>0){
#pragma region Go Boom
float radius{"Curse of Doom"_ENC["EXPLODE RANGE"]/100.f*24};
const HurtList list{game->Hurt(pos,radius,accumulatedCurseOfDeathDamage,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
for(const auto&[targetPtr,wasHit]:list){
if(wasHit){
std::get<Monster*>(targetPtr)->ProximityKnockback(pos,"Curse of Doom"_ENC["EXPLODE KNOCKBACK AMOUNT"]);
std::get<Monster*>(targetPtr)->Knockup("Curse of Doom"_ENC["EXPLODE KNOCKUP AMOUNT"]);
float radius{"Curse of Doom"_ENC["EXPLODE RANGE"]/100.f*24};
const HurtList list{game->Hurt(pos,radius,accumulatedCurseOfDeathDamage,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
for(const auto&[targetPtr,wasHit]:list){
if(wasHit){
std::get<Monster*>(targetPtr)->ProximityKnockback(pos,"Curse of Doom"_ENC["EXPLODE KNOCKBACK AMOUNT"]);
std::get<Monster*>(targetPtr)->Knockup("Curse of Doom"_ENC["EXPLODE KNOCKUP AMOUNT"]);
}
}
}
float targetScale{radius/24};
float startingScale{GetCollisionRadius()/24};
std::unique_ptr<Effect>explodeEffect{std::make_unique<Effect>(pos,ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration()-0.2f,"explosionframes.png",OnUpperLevel(),startingScale,0.2f,vf2d{0,-6.f},WHITE,util::random(2*PI),0.f)};
explodeEffect->scaleSpd={(targetScale-startingScale)/ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration(),(targetScale-startingScale)/ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration()};
game->AddEffect(std::move(explodeEffect));
SoundEffect::PlaySFX("Explosion",pos);
float targetScale{radius/24};
float startingScale{GetCollisionRadius()/24};
std::unique_ptr<Effect>explodeEffect{std::make_unique<Effect>(pos,ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration()-0.2f,"explosionframes.png",OnUpperLevel(),startingScale,0.2f,vf2d{0,-6.f},WHITE,util::random(2*PI),0.f)};
explodeEffect->scaleSpd={(targetScale-startingScale)/ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration(),(targetScale-startingScale)/ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration()};
game->AddEffect(std::move(explodeEffect));
SoundEffect::PlaySFX("Explosion",pos);
#pragma endregion
}
@ -1354,7 +1356,7 @@ const bool Monster::Immovable()const{
return MONSTER_DATA.at(GetName()).Immovable();
}
const bool Monster::Invulnerable()const{
return MONSTER_DATA.at(GetName()).Invulnerable();
return MONSTER_DATA.at(GetName()).Invulnerable()||IsUnconscious();
}
const std::optional<float>Monster::GetLifetime()const{
return lifetime;
@ -1644,3 +1646,9 @@ void Monster::MoveForward(const vf2d&moveForwardVec,const float fElapsedTime){
if(moveForwardVec.mag()==0.f)ERR("WARNING! Passed a zero length vector into Move Forward! THIS IS NOT ALLOWED!");
SetPos(pos+moveForwardVec.norm()*100.f*fElapsedTime*GetMoveSpdMult());
}
const bool Monster::IsUnconscious()const{
return unconsciousTimer>0.f;
}
const float Monster::UnconsciousTime()const{
return MONSTER_DATA.at(name).UnconsciousTime();
}

@ -91,7 +91,7 @@ public:
float GetSizeMult()const;
Animate2D::Frame GetFrame()const;
const std::optional<const Animate2D::Frame>GetMountedFrame()const;
bool Update(float fElapsedTime);
void Update(const 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(HurtDamageInfo damageData);
@ -321,6 +321,9 @@ private:
int accumulatedCurseOfDeathDamage{};
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{};
const bool IsUnconscious()const;
const float UnconsciousTime()const;
struct STRATEGY{
static std::string ERR;

@ -200,6 +200,8 @@ void MonsterData::InitializeMonsterData(){
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();
if(DATA["Monsters"][MonsterName].HasProperty("Unconscious Time"))monster.totalUnconsciousTime=DATA["Monsters"][MonsterName]["Unconscious Time"].GetReal();
if(DATA["Monsters"][MonsterName].HasProperty("Rectangle Collision")){
const datafile&rectData{DATA["Monsters"][MonsterName]["Rectangle Collision"]};
monster.rectCollision={{rectData.GetReal(0),rectData.GetReal(1)},{rectData.GetReal(2),rectData.GetReal(3)}};
@ -476,3 +478,6 @@ const bool MonsterData::FadeoutWhenStandingBehind()const{
const bool MonsterData::FaceTarget()const{
return !noFacing;
}
const float MonsterData::UnconsciousTime()const{
return totalUnconsciousTime;
}

@ -108,6 +108,7 @@ public:
const std::optional<geom2d::rect<float>>&GetRectangleCollision()const;
const bool FadeoutWhenStandingBehind()const;
const bool FaceTarget()const;
const float UnconsciousTime()const;
private:
std::string name;
int hp;
@ -140,4 +141,5 @@ private:
float collisionRadius{};
bool hasArrowIndicator{false};
std::optional<geom2d::rect<float>>rectCollision;
float totalUnconsciousTime{};
};

@ -48,8 +48,4 @@ INCLUDE_game
void Monster::STRATEGY::PARROT(Monster&m,float fElapsedTime,std::string strategy){
HAWK(m,fElapsedTime,"Hawk");
m.SetStrategyDeathFunction([](GameEvent&event,Monster&monster,const std::string&strategyName){
return true;
});
}

@ -71,7 +71,6 @@ void Monster::STRATEGY::PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string
switch(m.phase){
case INIT:{
m.F(A::TARGET_TIMER)=ConfigFloat("Shooting Frequency");
m.F(A::WAIT_TIMER)=ConfigFloat("");
m.mounted_animation=Animate2D::Animation<std::string>{};
for(bool firstAnimation=true;const std::string&animation:Config("Imposed Monster Animations").GetValues()){
m.mounted_animation.value().AddState(animation,ANIMATION_DATA.at(animation));

Loading…
Cancel
Save