Add UpperZone and LowerZone TMXParser's Map Constructor. This way we don't have to specify the zones ourselves creating invalid maps during testing. Added Reaper of Souls enchant unit test. Add unit tests for equipping two rings of the same enchant. Unique and class enchants behavior modified to not double up. 128/128 unit tests passing. Release Build 10694.

mac-build
sigonasr2 4 months ago
parent 9a65b731e9
commit 300e9834c7
  1. 90
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 2
      Adventures in Lestoria Tests/MonsterTests.cpp
  3. 18
      Adventures in Lestoria/AdventuresInLestoria.cpp
  4. 4
      Adventures in Lestoria/AdventuresInLestoria.h
  5. 1
      Adventures in Lestoria/Effect.h
  6. 4
      Adventures in Lestoria/MonsterSoul.cpp
  7. 6
      Adventures in Lestoria/Player.cpp
  8. 8
      Adventures in Lestoria/TMXParser.h
  9. 2
      Adventures in Lestoria/Version.h
  10. BIN
      x64/Release/Adventures in Lestoria.exe

@ -109,79 +109,128 @@ namespace EnchantTests
}
TEST_METHOD(HealthBoostCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(100,player->GetMaxHealth(),L"Player starts with 100 health.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Health Boost");
Test::InRange(player->GetMaxHealth(),{103,105},L"Max Health not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Health Boost");
Test::InRange(player->GetMaxHealth(),{106,110},L"Max Health not in expected range with two rings equipped.");
}
}
TEST_METHOD(AttackBoostCheck){
player->SetBaseStat("Attack",100.f);
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(100,player->GetAttack(),L"Player starts with 100 attack.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Attack Boost");
Test::InRange(player->GetAttack(),{103,105},L"Attack not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Attack Boost");
Test::InRange(player->GetAttack(),{106,110},L"Attack not in expected range with two rings equipped.");
}
}
TEST_METHOD(MovementBoostCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(100.0_Pct,player->GetMoveSpdMult(),L"Player starts with 100% Movespd.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Movement Boost");
Test::InRange(player->GetMoveSpdMult(),{103.0_Pct,105.0_Pct},L"Move Speed not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Movement Boost");
Test::InRange(player->GetMoveSpdMult(),{106.0_Pct,110.0_Pct},L"Move Speed not in expected range with two rings equipped.");
}
}
TEST_METHOD(AbilityHasteCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(0.0_Pct,player->GetCooldownReductionPct(),L"Player starts with 0% CDR.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Ability Haste");
Test::InRange(player->GetCooldownReductionPct(),{3.0_Pct,5.0_Pct},L"CDR not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Ability Haste");
Test::InRange(player->GetCooldownReductionPct(),{6.0_Pct,10.0_Pct},L"CDR not in expected range with two rings.");
}
}
TEST_METHOD(CritRateCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(0.0_Pct,player->GetCritRatePct(),L"Player starts with 0% Crit Rate.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Crit Rate");
Test::InRange(player->GetCritRatePct(),{3.0_Pct,5.0_Pct},L"Crit Rate not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Crit Rate");
Test::InRange(player->GetCritRatePct(),{6.0_Pct,10.0_Pct},L"Crit Rate not in expected range with two rings.");
}
}
TEST_METHOD(CritDamageCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(50.0_Pct,player->GetCritDmgPct(),L"Player starts with 50% Crit Damage.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Crit Damage");
Test::InRange(player->GetCritDmgPct(),{57.0_Pct,60.0_Pct},L"Crit Damage not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Crit Damage");
Test::InRange(player->GetCritDmgPct(),{64.0_Pct,70.0_Pct},L"Crit Damage not in expected range with two rings.");
}
}
TEST_METHOD(StoneskinCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Player starts with 0% Damage Reduction.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Stoneskin");
Test::InRange(player->GetDamageReductionPct(),{3.0_Pct,5.0_Pct},L"Damage Reduction not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Stoneskin");
Test::InRange(player->GetDamageReductionPct(),{6.0_Pct,10.0_Pct},L"Damage Reduction not in expected range with two rings.");
}
}
TEST_METHOD(ManaPoolCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(100,player->GetMaxMana(),L"Player starts with 100 mana.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Mana Pool");
Test::InRange(player->GetMaxMana(),{107,112},L"Mana Pool not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Mana Pool");
Test::InRange(player->GetMaxMana(),{114,124},L"Mana Pool not in expected range with two rings.");
}
}
TEST_METHOD(MagicalProtectionCheck){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(100,player->GetMaxHealth(),L"Player starts with 100 health.");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Player starts with 0% damage reduction.");
Assert::AreEqual(100.0_Pct,player->GetMoveSpdMult(),L"Player starts with 100% move speed.");
@ -194,10 +243,19 @@ namespace EnchantTests
Test::InRange(player->GetMoveSpdMult(),{102.0_Pct,103.0_Pct},L"Move Speed % not in expected range.");
Test::InRange(player->GetHP6RecoveryPct(),{1.0_Pct,1.0_Pct},L"HP/6 Recovery not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Magical Protection");
Test::InRange(player->GetMaxHealth(),{102,103},L"Max Health not in expected range with two rings.");
Test::InRange(player->GetDamageReductionPct(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range with two rings.");
Test::InRange(player->GetMoveSpdMult(),{102.0_Pct,103.0_Pct},L"Move Speed % not in expected range with two rings.");
Test::InRange(player->GetHP6RecoveryPct(),{1.0_Pct,1.0_Pct},L"HP/6 Recovery not in expected range with two rings.");
}
}
TEST_METHOD(AuraOfTheBeastCheck){
player->SetBaseStat("Attack",100.f);
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
std::weak_ptr<Item>nullRing2{Inventory::AddItem("Null Ring"s)};
Assert::AreEqual(100,player->GetAttack(),L"Player starts with 100 attack.");
Assert::AreEqual(0.0_Pct,player->GetCritRatePct(),L"Player starts with 0% crit rate.");
Assert::AreEqual(0.0_Pct,player->GetCooldownReductionPct(),L"Player starts with 0% cooldown reduction.");
@ -210,6 +268,14 @@ namespace EnchantTests
Test::InRange(player->GetCooldownReductionPct(),{2.0_Pct,3.0_Pct},L"Cooldown Reduction % not in expected range.");
Test::InRange(player->GetCritDmgPct(),{53.0_Pct,57.0_Pct},L"Crit Damage not in expected range.");
}
Inventory::EquipItem(nullRing2,EquipSlot::RING2);
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->EnchantItem("Aura of the Beast");
Test::InRange(player->GetAttack(),{102,103},L"Attack not in expected range with two rings.");
Test::InRange(player->GetCritRatePct(),{2.0_Pct,3.0_Pct},L"Crit Rate not in expected range with two rings.");
Test::InRange(player->GetCooldownReductionPct(),{2.0_Pct,3.0_Pct},L"Cooldown Reduction % not in expected range with two rings.");
Test::InRange(player->GetCritDmgPct(),{53.0_Pct,57.0_Pct},L"Crit Damage not in expected range with two rings.");
}
}
TEST_METHOD(LethalTempoCheck){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
@ -291,5 +357,29 @@ namespace EnchantTests
}
Assert::AreEqual(true,survivedAtLeastOnce,L"Player should have survived at least one time with Death Defiance.");
}
TEST_METHOD(ReaperOfSoulsCheck){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
Monster testMonster{{},MONSTER_DATA["TestName"]};
Monster testMonster2{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(1000,testMonster.OnUpperLevel(),testMonster.GetZ());
testGame->SetElapsedTime(0.5f);
testGame->OnUserUpdate(0.5f);
for(Effect*eff:game->GetAllEffects()|std::views::filter([](Effect*eff){return eff->GetType()==EffectType::MONSTER_SOUL;})){
Assert::Fail(L"A Monster Soul should not be generated");
}
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Reaper of Souls");
testMonster2.Hurt(1000,testMonster2.OnUpperLevel(),testMonster2.GetZ());
testGame->SetElapsedTime(0.5f);
testGame->OnUserUpdate(0.5f);
bool foundSoul{false};
for(const Effect*eff:game->GetAllEffects()|std::views::filter([](const Effect*eff){return eff->GetType()==EffectType::MONSTER_SOUL;})){
foundSoul=true;
break;
}
if(!foundSoul)Assert::Fail(L"A soul was not generated from a kill with the Reaper of Souls enchant.");
}
};
}

@ -85,8 +85,6 @@ namespace MonsterTests
#pragma region Setup a fake test map
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["UpperZone"];
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["LowerZone"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
#pragma endregion

@ -4529,4 +4529,22 @@ void AiL::AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,Mar
void AiL::_SetCurrentLevel(const MapName map){
currentLevel=map;
}
std::vector<Effect*>AiL::GetForegroundEffects()const{
std::vector<Effect*>outputVec;
std::for_each(foregroundEffects.begin(),foregroundEffects.end(),[&outputVec](const std::unique_ptr<Effect>&eff){outputVec.emplace_back(eff.get());});
return outputVec;
}
std::vector<Effect*>AiL::GetBackgroundEffects()const{
std::vector<Effect*>outputVec;
std::for_each(backgroundEffects.begin(),backgroundEffects.end(),[&outputVec](const std::unique_ptr<Effect>&eff){outputVec.emplace_back(eff.get());});
return outputVec;
}
std::vector<Effect*>AiL::GetAllEffects()const{
std::vector<Effect*>outputVec;
std::vector<Effect*>foregroundEffects{GetForegroundEffects()};
std::vector<Effect*>backgroundEffects{GetBackgroundEffects()};
std::merge(foregroundEffects.begin(),foregroundEffects.end(),backgroundEffects.begin(),backgroundEffects.end(),std::back_inserter(outputVec));
return outputVec;
}

@ -387,6 +387,10 @@ public:
void InitializeClasses();
void _SetCurrentLevel(const MapName map); //NOTE: This will modify the currentLevel variable without triggering anything else in-game, this will normally mess up the state in the game. Ideally this is only used when initializing a test level.
std::vector<Effect*>GetForegroundEffects()const;
std::vector<Effect*>GetBackgroundEffects()const;
std::vector<Effect*>GetAllEffects()const; //Foreground and background effects in one vector.
struct TileGroupData{
vi2d tilePos;
int layer;

@ -46,6 +46,7 @@ using HitList=std::unordered_set<std::variant<Monster*,Player*>>;
enum class EffectType{
NONE,
SPELL_CIRCLE,
MONSTER_SOUL,
};
struct Effect{

@ -45,7 +45,9 @@ INCLUDE_game
INCLUDE_GFX
MonsterSoul::MonsterSoul(vf2d pos,float fadeoutTime,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(pos,INFINITE,"monstersoul.png",OnUpperLevel(),size,fadeoutTime,spd,col,rotation,rotationSpd,additiveBlending),fadeoutTime(fadeoutTime){}
:Effect(pos,INFINITE,"monstersoul.png",OnUpperLevel(),size,fadeoutTime,spd,col,rotation,rotationSpd,additiveBlending),fadeoutTime(fadeoutTime){
type=EffectType::MONSTER_SOUL;
}
bool MonsterSoul::Update(float fElapsedTime){
bool updateResult{Effect::Update(fElapsedTime)};
switch(phase){

@ -1324,7 +1324,11 @@ void EntityStats::RecalculateEquipStats(){
for(auto&[key,size]:ItemAttribute::attributes){
equipStats.A(key)+=equip.lock()->GetStats().A_Read(key);
equipStats.A(key)+=equip.lock()->RandomStats().A_Read(key);
if(equip.lock()->HasEnchant())equipStats.A(key)+=equip.lock()->GetEnchant().value().A_Read(key);
if(slot==EquipSlot::RING2&&equip.lock()->HasEnchant()){ //Special doubling-up ring logic.
const bool IsNonCommonEnchant{equip.lock()->HasEnchant()&&equip.lock()->GetEnchant().value().Category()!=ItemEnchantInfo::ItemEnchantCategory::GENERAL};
const bool Slot1HasSameEnchant{!ISBLANK(Inventory::GetEquip(EquipSlot::RING1))&&Inventory::GetEquip(EquipSlot::RING1).lock()->HasEnchant()&&Inventory::GetEquip(EquipSlot::RING1).lock()->GetEnchant().value().Name()==equip.lock()->GetEnchant().value().Name()};
if(!IsNonCommonEnchant||!Slot1HasSameEnchant)equipStats.A(key)+=equip.lock()->GetEnchant().value().A_Read(key);
}else if(equip.lock()->HasEnchant())equipStats.A(key)+=equip.lock()->GetEnchant().value().A_Read(key);
}
}

@ -125,7 +125,6 @@ namespace MonsterTests{
struct Map{
friend class AiL;
friend class TMXParser;
friend class MonsterTests::MonsterTest;
private:
MapTag MapData;
std::string name;
@ -142,8 +141,9 @@ private:
std::set<std::string>spawns;
std::map<int,SpawnerTag>SpawnerData; //Spawn groups have IDs, mobs associate which spawner they are tied to via this ID.
std::optional<std::queue<MonsterSpawnerID>>spawnControllerIDs;
std::map<std::string,std::vector<::ZoneData>> ZoneData;
std::map<std::string,std::vector<::ZoneData>>ZoneData;
public:
Map();
bool skipLoadoutScreen=false;
const MapTag&GetMapData()const;
const std::string_view GetMapType()const;
@ -264,6 +264,10 @@ class TMXParser{
return true;
}
}
Map::Map(){
ZoneData["UpperZone"];
ZoneData["LowerZone"];
}
MapTag::MapTag(){}
MapTag::MapTag(int width,int height,int tilewidth,int tileheight)
:width(width),height(height),tilewidth(tilewidth),tileheight(tileheight),MapSize({width,height}),TileSize({tilewidth,tileheight}){}

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 3
#define VERSION_BUILD 10683
#define VERSION_BUILD 10694
#define stringify(a) stringify_(a)
#define stringify_(a) #a

Loading…
Cancel
Save