Fix bug with multi-hit bullets not adding hit targets to the hit list before monster hit / player hit bullet callbacks. Implemented Piercing Bolt enchant. 199/199 unit tests passing. Release Build 11200.

pull/65/head
sigonasr2 3 months ago
parent 6d4de7940b
commit 0288ccbd0c
  1. 42
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 4
      Adventures in Lestoria Tests/GameHelper.h
  3. 5
      Adventures in Lestoria/EnergyBolt.cpp
  4. 9
      Adventures in Lestoria/ExplosiveTrap.cpp
  5. 4
      Adventures in Lestoria/IBullet.cpp
  6. 4
      Adventures in Lestoria/Player.cpp
  7. 1
      Adventures in Lestoria/PurpleEnergyBall.cpp
  8. 2
      Adventures in Lestoria/Version.h
  9. BIN
      x64/Release/Adventures in Lestoria.exe

@ -97,7 +97,7 @@ namespace EnchantTests
#pragma region Setup a fake test map and test monster #pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"]; game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA["CAMPAIGN_1_1"]._SetMapData(MapTag{50,50,24,24}); game->MAP_DATA["CAMPAIGN_1_1"]._SetMapData(MapTag{24*24,24*24,24,24});
ItemDrop::ClearDrops(); ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f}; MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData; MONSTER_DATA["TestName"]=testMonsterData;
@ -577,6 +577,7 @@ namespace EnchantTests
} }
TEST_METHOD(TripleTossCheck){ TEST_METHOD(TripleTossCheck){
Game::ChangeClass(player,THIEF); Game::ChangeClass(player,THIEF);
player->SetTestScreenAimingLocation(player->GetPos()+vf2d{30.f,0.f});
player->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput); player->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput);
Game::Update(0.5f); Game::Update(0.5f);
Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"Only 1 dagger has spawned without the Triple Toss enchant."); Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"Only 1 dagger has spawned without the Triple Toss enchant.");
@ -839,6 +840,7 @@ namespace EnchantTests
Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"An Explosive Trap still exists (in the process of detonating a few times)"); Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"An Explosive Trap still exists (in the process of detonating a few times)");
Assert::AreEqual(721,newMonster.GetHealth(),L"Monster takes 104 + 104 damage from Concussive Trap bonus (Collision+Collateral Explosion)."); Assert::AreEqual(721,newMonster.GetHealth(),L"Monster takes 104 + 104 damage from Concussive Trap bonus (Collision+Collateral Explosion).");
Game::Update(5.5f); Game::Update(5.5f);
Game::Update(5.5f);
Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"Explosive Trap detonates later."); Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"Explosive Trap detonates later.");
} }
TEST_METHOD(SwordEnchantmentNoEnchantCheck){ TEST_METHOD(SwordEnchantmentNoEnchantCheck){
@ -966,12 +968,13 @@ namespace EnchantTests
} }
TEST_METHOD(BouncingOrbEnchantCheck){ TEST_METHOD(BouncingOrbEnchantCheck){
Game::ChangeClass(player,WITCH); Game::ChangeClass(player,WITCH);
player->ForceSetPos({50.f,0.f}); player->ForceSetPos({20.f,50.f});
Monster&newMonster{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])}; Monster&newMonster{game->SpawnMonster({30.f,50.f},MONSTER_DATA["TestName"])};
Monster&newMonster2{game->SpawnMonster({50.f,0.f},MONSTER_DATA["TestName"])}; Monster&newMonster2{game->SpawnMonster({50.f,50.f},MONSTER_DATA["TestName"])};
Game::GiveAndEquipEnchantedRing("Bouncing Orb"); Game::GiveAndEquipEnchantedRing("Bouncing Orb");
Assert::AreEqual(1000,newMonster.GetHealth(),L"Monster 1 is at 1000 HP."); Assert::AreEqual(1000,newMonster.GetHealth(),L"Monster 1 is at 1000 HP.");
Assert::AreEqual(1000,newMonster2.GetHealth(),L"Monster 2 is at 1000 HP."); Assert::AreEqual(1000,newMonster2.GetHealth(),L"Monster 2 is at 1000 HP.");
player->SetTestScreenAimingLocation(player->GetPos()+vf2d{30.f,0.f});
player->AutoAttack(); player->AutoAttack();
for(int i:std::ranges::iota_view(0,50)){ for(int i:std::ranges::iota_view(0,50)){
game->SetElapsedTime(0.1f); game->SetElapsedTime(0.1f);
@ -1150,5 +1153,36 @@ namespace EnchantTests
Assert::AreEqual(newMonster3.GetMaxHealth()-500,newMonster3.GetHealth(),L"Monster 3 takes collateral damage from Curse of Doom enchant based on the amount of damage dealt during Curse of Death debuff."); Assert::AreEqual(newMonster3.GetMaxHealth()-500,newMonster3.GetHealth(),L"Monster 3 takes collateral damage from Curse of Doom enchant based on the amount of damage dealt during Curse of Death debuff.");
Assert::AreEqual(newMonster4.GetMaxHealth()-500,newMonster4.GetHealth(),L"Monster 4 takes collateral damage from Curse of Doom enchant based on the amount of damage dealt during Curse of Death debuff."); Assert::AreEqual(newMonster4.GetMaxHealth()-500,newMonster4.GetHealth(),L"Monster 4 takes collateral damage from Curse of Doom enchant based on the amount of damage dealt during Curse of Death debuff.");
} }
TEST_METHOD(PiercingBoltNoEnchantCheck){
Game::ChangeClass(player,WIZARD);
Monster&newMonster{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])};
Monster&newMonster2{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])};
Monster&newMonster3{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])};
Monster&newMonster4{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])};
player->SetTestScreenAimingLocation(player->GetPos()+vf2d{30.f,0.f});
Game::Update(0.f);
player->AutoAttack();
Game::Update(0.5f);
Assert::AreEqual(985,newMonster.GetHealth(),L"Monster 1 takes normal damage from Wizard auto attack.");
Assert::AreEqual(1000,newMonster2.GetHealth(),L"Monster 2 should still be full health.");
Assert::AreEqual(1000,newMonster3.GetHealth(),L"Monster 3 should still be full health.");
Assert::AreEqual(1000,newMonster4.GetHealth(),L"Monster 4 should still be full health.");
}
TEST_METHOD(PiercingBoltEnchantCheck){
Game::ChangeClass(player,WIZARD);
Game::GiveAndEquipEnchantedRing("Piercing Bolt");
Monster&newMonster{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])};
Monster&newMonster2{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])};
Monster&newMonster3{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])};
Monster&newMonster4{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])};
player->SetTestScreenAimingLocation(player->GetPos()+vf2d{30.f,0.f});
Game::Update(0.f);
player->AutoAttack();
Game::Update(0.5f);
Assert::AreEqual(985,newMonster.GetHealth(),L"Monster 1 takes normal damage from Wizard auto attack.");
Assert::AreEqual(992,newMonster2.GetHealth(),L"Monster 2 should take half the damage the first target does with the Percing Bolt Enchant.");
Assert::AreEqual(992,newMonster3.GetHealth(),L"Monster 3 should take half the damage the first target does with the Percing Bolt Enchant");
Assert::AreEqual(992,newMonster4.GetHealth(),L"Monster 4 should take half the damage the first target does with the Percing Bolt Enchant");
}
}; };
} }

@ -50,8 +50,8 @@ namespace Game{
game->SetElapsedTime(fElapsedTime); game->SetElapsedTime(fElapsedTime);
game->OnUserUpdate(fElapsedTime); game->OnUserUpdate(fElapsedTime);
} }
inline void CastAbilityAtLocation(Ability&ability,const vf2d&screenLoc,const CastWaitProperty castWaitTime=CastWaitProperty::WAIT_FOR_CAST_TIME){ //NOTE: screenLoc is the actual screen coordinates, NOT the world coordinates! You are defining the mouse position essentially. inline void CastAbilityAtLocation(Ability&ability,const vf2d&worldLoc,const CastWaitProperty castWaitTime=CastWaitProperty::WAIT_FOR_CAST_TIME){ //NOTE: screenLoc is the actual screen coordinates, NOT the world coordinates! You are defining the mouse position essentially.
game->GetPlayer()->SetTestScreenAimingLocation(screenLoc); game->GetPlayer()->SetTestScreenAimingLocation(worldLoc);
game->GetPlayer()->PrepareCast(ability); game->GetPlayer()->PrepareCast(ability);
game->GetPlayer()->CastSpell(ability); game->GetPlayer()->CastSpell(ability);
Game::Update(ability.precastInfo.castTime); Game::Update(ability.precastInfo.castTime);

@ -45,7 +45,7 @@ INCLUDE_game
EnergyBolt::EnergyBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col) EnergyBolt::EnergyBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:Bullet(pos,vel,radius,damage, :Bullet(pos,vel,radius,damage,
"energy_bolt.png",upperLevel,false,INFINITE,true,friendly,col){} "energy_bolt.png",upperLevel,game->GetPlayer()->HasEnchant("Piercing Bolt")?true:false,INFINITE,true,friendly,col){}
void EnergyBolt::Update(float fElapsedTime){ void EnergyBolt::Update(float fElapsedTime){
lastParticleSpawn=std::max(0.f,lastParticleSpawn-fElapsedTime); lastParticleSpawn=std::max(0.f,lastParticleSpawn-fElapsedTime);
@ -67,8 +67,9 @@ BulletDestroyState EnergyBolt::PlayerHit(Player*player)
BulletDestroyState EnergyBolt::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit) BulletDestroyState EnergyBolt::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{ {
fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F; if(!game->GetPlayer()->HasEnchant("Piercing Bolt"))fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Auto Attack.SplashEffectFadeoutTime"_F)); game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Auto Attack.SplashEffectFadeoutTime"_F));
if(hitList.size()==1)damage=ceil(float(damage)/2);
return BulletDestroyState::KEEP_ALIVE; return BulletDestroyState::KEEP_ALIVE;
} }

@ -96,8 +96,10 @@ void ExplosiveTrap::Detonate(){
for(const auto&[targetPtr,wasHit]:list){ for(const auto&[targetPtr,wasHit]:list){
if(wasHit){ if(wasHit){
std::get<Monster*>(targetPtr)->ApplyMark("Trapper.Ability 3.Explosion Mark Stack Time"_F,"Trapper.Ability 3.Explosion Mark Stack Increase"_I); std::get<Monster*>(targetPtr)->ApplyMark("Trapper.Ability 3.Explosion Mark Stack Time"_F,"Trapper.Ability 3.Explosion Mark Stack Increase"_I);
std::get<Monster*>(targetPtr)->ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F); if(explosionCount==1)std::get<Monster*>(targetPtr)->ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
std::get<Monster*>(targetPtr)->Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F); else std::get<Monster*>(targetPtr)->ProximityKnockback(pos,20.f);
if(explosionCount==1)std::get<Monster*>(targetPtr)->Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
else std::get<Monster*>(targetPtr)->Knockup(0.1f);
} }
} }
@ -119,9 +121,6 @@ BulletDestroyState ExplosiveTrap::MonsterHit(Monster&monster,const uint8_t markS
monster.Hurt(0,monster.OnUpperLevel(),monster.GetZ(),HurtFlag::PLAYER_ABILITY);//Triggers mark multiple times after the first mark. monster.Hurt(0,monster.OnUpperLevel(),monster.GetZ(),HurtFlag::PLAYER_ABILITY);//Triggers mark multiple times after the first mark.
} }
monster.ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
monster.Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
Detonate(); Detonate();
return BulletDestroyState::KEEP_ALIVE; return BulletDestroyState::KEEP_ALIVE;

@ -106,13 +106,13 @@ void IBullet::_Update(const float fElapsedTime){
ModifyOutgoingDamageData(damageData); ModifyOutgoingDamageData(damageData);
uint8_t markStacksBeforeHit{m->GetMarkStacks()}; uint8_t markStacksBeforeHit{m->GetMarkStacks()};
if(m->Hurt(damageData)){ if(m->Hurt(damageData)){
hitList.insert(&*m);
if(!hitsMultiple){ if(!hitsMultiple){
if(_MonsterHit(*m,markStacksBeforeHit)==BulletDestroyState::DESTROY){ if(_MonsterHit(*m,markStacksBeforeHit)==BulletDestroyState::DESTROY){
dead=true; dead=true;
} }
return false; return false;
}else _MonsterHit(*m,markStacksBeforeHit); }else _MonsterHit(*m,markStacksBeforeHit);
hitList.insert(&*m);
if(!CollisionCheckRequired())return false; if(!CollisionCheckRequired())return false;
} }
} }
@ -125,13 +125,13 @@ void IBullet::_Update(const float fElapsedTime){
//NOTE: OnHurt() will potentially change the Damage Data, passing it along to bullet children that might need to modify the flags! //NOTE: OnHurt() will potentially change the Damage Data, passing it along to bullet children that might need to modify the flags!
ModifyOutgoingDamageData(damageData); ModifyOutgoingDamageData(damageData);
if(game->GetPlayer()->Hurt(damageData)){ if(game->GetPlayer()->Hurt(damageData)){
hitList.insert(game->GetPlayer());
if(!hitsMultiple){ if(!hitsMultiple){
if(_PlayerHit(&*game->GetPlayer())==BulletDestroyState::DESTROY){ if(_PlayerHit(&*game->GetPlayer())==BulletDestroyState::DESTROY){
dead=true; dead=true;
} }
return false; return false;
}else _PlayerHit(&*game->GetPlayer()); }else _PlayerHit(&*game->GetPlayer());
hitList.insert(game->GetPlayer());
if(!CollisionCheckRequired())return false; if(!CollisionCheckRequired())return false;
} }
} }

@ -1760,7 +1760,9 @@ void Player::Knockup(float duration){
} }
const vf2d Player::GetWorldAimingLocation(bool useWalkDir,bool invert){ const vf2d Player::GetWorldAimingLocation(bool useWalkDir,bool invert){
const vf2d screenAimingLoc{game->view.ScreenToWorld(GetAimingLocation(useWalkDir,invert))}; vf2d screenAimingLoc{game->view.ScreenToWorld(GetAimingLocation(useWalkDir,invert))};
if(game->TestingModeEnabled())screenAimingLoc=testAimingLoc.value_or(GetPos()+vf2d{});
if(screenAimingLoc==GetPos())return GetPos()+vf2d{0,-1.f}; if(screenAimingLoc==GetPos())return GetPos()+vf2d{0,-1.f};
else return screenAimingLoc; else return screenAimingLoc;

@ -104,6 +104,7 @@ BulletDestroyState PurpleEnergyBall::MonsterHit(Monster&monster,const uint8_t ma
bounceCount--; bounceCount--;
hitList.clear(); hitList.clear();
lastHitTarget=monster.GetWeakPointer(); lastHitTarget=monster.GetWeakPointer();
hitList.emplace(&monster);
if(bounceCount<=0)fadeOutTime="Witch.Auto Attack.BulletHitFadeoutTime"_F; if(bounceCount<=0)fadeOutTime="Witch.Auto Attack.BulletHitFadeoutTime"_F;
else lastHitTimer=0.1f; else lastHitTimer=0.1f;
Deactivate(); Deactivate();

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1 #define VERSION_MAJOR 1
#define VERSION_MINOR 2 #define VERSION_MINOR 2
#define VERSION_PATCH 5 #define VERSION_PATCH 5
#define VERSION_BUILD 11188 #define VERSION_BUILD 11200
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

Loading…
Cancel
Save