diff --git a/Adventures in Lestoria Tests/EnchantTests.cpp b/Adventures in Lestoria Tests/EnchantTests.cpp index 767faa27..6437c600 100644 --- a/Adventures in Lestoria Tests/EnchantTests.cpp +++ b/Adventures in Lestoria Tests/EnchantTests.cpp @@ -109,79 +109,128 @@ namespace EnchantTests } TEST_METHOD(HealthBoostCheck){ std::weak_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + std::weak_ptrnullRing2{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_ptrnullRing{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."); + } }; } \ No newline at end of file diff --git a/Adventures in Lestoria Tests/MonsterTests.cpp b/Adventures in Lestoria Tests/MonsterTests.cpp index ad629582..9785fdd2 100644 --- a/Adventures in Lestoria Tests/MonsterTests.cpp +++ b/Adventures in Lestoria Tests/MonsterTests.cpp @@ -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 diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index 0254ff26..293f1191 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -4529,4 +4529,22 @@ void AiL::AddToMarkedTargetList(std::tuple,StackCount,Mar void AiL::_SetCurrentLevel(const MapName map){ currentLevel=map; +} + +std::vectorAiL::GetForegroundEffects()const{ + std::vectoroutputVec; + std::for_each(foregroundEffects.begin(),foregroundEffects.end(),[&outputVec](const std::unique_ptr&eff){outputVec.emplace_back(eff.get());}); + return outputVec; +} +std::vectorAiL::GetBackgroundEffects()const{ + std::vectoroutputVec; + std::for_each(backgroundEffects.begin(),backgroundEffects.end(),[&outputVec](const std::unique_ptr&eff){outputVec.emplace_back(eff.get());}); + return outputVec; +} +std::vectorAiL::GetAllEffects()const{ + std::vectoroutputVec; + std::vectorforegroundEffects{GetForegroundEffects()}; + std::vectorbackgroundEffects{GetBackgroundEffects()}; + std::merge(foregroundEffects.begin(),foregroundEffects.end(),backgroundEffects.begin(),backgroundEffects.end(),std::back_inserter(outputVec)); + return outputVec; } \ No newline at end of file diff --git a/Adventures in Lestoria/AdventuresInLestoria.h b/Adventures in Lestoria/AdventuresInLestoria.h index b748f794..f281ee59 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.h +++ b/Adventures in Lestoria/AdventuresInLestoria.h @@ -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::vectorGetForegroundEffects()const; + std::vectorGetBackgroundEffects()const; + std::vectorGetAllEffects()const; //Foreground and background effects in one vector. + struct TileGroupData{ vi2d tilePos; int layer; diff --git a/Adventures in Lestoria/Effect.h b/Adventures in Lestoria/Effect.h index 2007cfef..9d60f64a 100644 --- a/Adventures in Lestoria/Effect.h +++ b/Adventures in Lestoria/Effect.h @@ -46,6 +46,7 @@ using HitList=std::unordered_set>; enum class EffectType{ NONE, SPELL_CIRCLE, + MONSTER_SOUL, }; struct Effect{ diff --git a/Adventures in Lestoria/MonsterSoul.cpp b/Adventures in Lestoria/MonsterSoul.cpp index a3af2637..befafc4a 100644 --- a/Adventures in Lestoria/MonsterSoul.cpp +++ b/Adventures in Lestoria/MonsterSoul.cpp @@ -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){ diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index 2355f0c2..d9722427 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -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); } } diff --git a/Adventures in Lestoria/TMXParser.h b/Adventures in Lestoria/TMXParser.h index 480b7ddc..96443fd2 100644 --- a/Adventures in Lestoria/TMXParser.h +++ b/Adventures in Lestoria/TMXParser.h @@ -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::setspawns; std::mapSpawnerData; //Spawn groups have IDs, mobs associate which spawner they are tied to via this ID. std::optional>spawnControllerIDs; - std::map> ZoneData; + std::map>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}){} diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index f51a1c73..bff88869 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 10683 +#define VERSION_BUILD 10694 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index bd0a08b1..0a7d2fe3 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ