Added stat-based item enchant checks. Remove friend class dependencies from unit tests and added appropriate publicly exposed functions. Release Build 10538.

mac-build
sigonasr2 4 months ago
parent d4d325503e
commit 2f244e21ff
  1. 4
      Adventures in Lestoria Tests/Adventures in Lestoria Tests.vcxproj
  2. 3
      Adventures in Lestoria Tests/Adventures in Lestoria Tests.vcxproj.filters
  3. 213
      Adventures in Lestoria Tests/EnchantTests.cpp
  4. 11
      Adventures in Lestoria Tests/ItemTests.cpp
  5. 6
      Adventures in Lestoria Tests/MonsterTests.cpp
  6. 4
      Adventures in Lestoria Tests/PlayerTests.cpp
  7. 4
      Adventures in Lestoria/AdventuresInLestoria.cpp
  8. 6
      Adventures in Lestoria/AdventuresInLestoria.h
  9. 4
      Adventures in Lestoria/ItemDrop.cpp
  10. 4
      Adventures in Lestoria/ItemDrop.h
  11. 12
      Adventures in Lestoria/ItemEnchant.cpp
  12. 1
      Adventures in Lestoria/Player.cpp
  13. 2
      Adventures in Lestoria/Version.h
  14. 7
      Adventures in Lestoria/olcPixelGameEngine.h
  15. BIN
      x64/Release/Adventures in Lestoria.exe
  16. 2
      x64/Unit Testing/assets/config/items/items-test.txt

@ -101,6 +101,10 @@
<ClCompile Include="..\Adventures in Lestoria\discord-files\store_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\user_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\voice_manager.cpp" />
<ClCompile Include="EnchantTests.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GeometryTests.cpp" />
<ClCompile Include="ItemTests.cpp">
<SubType>

@ -69,5 +69,8 @@
<ClCompile Include="ItemTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="EnchantTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

@ -0,0 +1,213 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
#include "Tutorial.h"
#include <random>
#include <format>
#include "ItemDrop.h"
#include "DamageNumber.h"
#include <ranges>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
extern std::mt19937 rng;
namespace Test
{
template<class T>
inline static void InRange(T initialVal,std::pair<T,T>range,std::wstring_view assertMessage){
Assert::IsTrue(initialVal>=range.first&&initialVal<=range.second,std::format(L"Expected: {}~{} Actual: {} - {}",range.first,range.second,initialVal,assertMessage).c_str());
}
}
namespace EnchantTests
{
TEST_CLASS(EnchantTest)
{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
const HWButton*testKey;
TEST_METHOD_INITIALIZE(PlayerInitialize){
rng=std::mt19937{57189U};//Establish a fixed random seed on setup so the exact same results are generated every test run.
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
#pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
#pragma endregion
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
testKey=testGame->GetKeyboardState(0);
testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work.
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
}
TEST_METHOD(HealthBoostCheck){
std::weak_ptr<Item>nullRing{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.");
}
}
TEST_METHOD(AttackBoostCheck){
player->SetBaseStat("Attack",100.f);
std::weak_ptr<Item>nullRing{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.");
}
}
TEST_METHOD(MovementBoostCheck){
std::weak_ptr<Item>nullRing{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.");
}
}
TEST_METHOD(AbilityHasteCheck){
std::weak_ptr<Item>nullRing{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.");
}
}
TEST_METHOD(CritRateCheck){
std::weak_ptr<Item>nullRing{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.");
}
}
TEST_METHOD(CritDamageCheck){
std::weak_ptr<Item>nullRing{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.");
}
}
TEST_METHOD(StoneskinCheck){
std::weak_ptr<Item>nullRing{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.");
}
}
TEST_METHOD(ManaPoolCheck){
std::weak_ptr<Item>nullRing{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.");
}
}
TEST_METHOD(MagicalProtectionCheck){
std::weak_ptr<Item>nullRing{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.");
Assert::AreEqual(0.0_Pct,player->GetHP6RecoveryPct(),L"Player starts with 0% HP/6 recovery.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Magical Protection");
Test::InRange(player->GetMaxHealth(),{102,103},L"Max Health not in expected range.");
Test::InRange(player->GetDamageReductionPct(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range.");
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.");
}
}
TEST_METHOD(AuraOfTheBeastCheck){
player->SetBaseStat("Attack",100.f);
std::weak_ptr<Item>nullRing{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.");
Assert::AreEqual(50.0_Pct,player->GetCritDmgPct(),L"Player starts with 50% crit rate.");
Inventory::EquipItem(nullRing,EquipSlot::RING1);
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->EnchantItem("Aura of the Beast");
Test::InRange(player->GetAttack(),{102,103},L"Attack not in expected range.");
Test::InRange(player->GetCritRatePct(),{2.0_Pct,3.0_Pct},L"Crit Rate not in expected range.");
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.");
}
}
};
}

@ -74,21 +74,20 @@ namespace ItemTests
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
#pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::drops.clear();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
#pragma endregion
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
testKey=&testGame->pKeyboardState[0];
testKey=testGame->GetKeyboardState(0);
testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work.
Menu::themes.SetInitialized();
GFX.SetInitialized();

@ -85,8 +85,8 @@ namespace MonsterTests
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["UpperZone"];
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["LowerZone"];
game->currentLevel="CAMPAIGN_1_1";
ItemDrop::drops.clear();
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
@ -98,7 +98,7 @@ namespace MonsterTests
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::drops.clear();
ItemDrop::ClearDrops();
}
#pragma endregion

@ -81,7 +81,7 @@ namespace PlayerTests
#pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::drops.clear();
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
#pragma endregion
@ -89,7 +89,7 @@ namespace PlayerTests
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
testKey=&testGame->pKeyboardState[0];
testKey=testGame->GetKeyboardState(0);
testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work.
Menu::themes.SetInitialized();
GFX.SetInitialized();

@ -4447,4 +4447,8 @@ const std::map<std::string,TilesetData>&AiL::GetTilesets()const{
void AiL::AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData){
lockOnTargets.emplace_back(markData);
}
void AiL::_SetCurrentLevel(const MapName map){
currentLevel=map;
}

@ -95,9 +95,6 @@ class AiL : public olc::PixelGameEngine
friend class sig::Animation;
friend class Audio;
friend class Minimap;
friend class MonsterTests::MonsterTest;
friend class PlayerTests::PlayerTest;
friend class ItemTests::ItemTest;
std::unique_ptr<Player>player;
SplashScreen splash;
public:
@ -170,7 +167,6 @@ private:
std::vector<TileGroup>upperForegroundTileGroups;
int bridgeLayerIndex=-1;
float bridgeFadeFactor=0.f;
void InitializeClasses();
int DEBUG_PATHFINDING=0;
std::vector<Monster*>monstersBeforeLower,monstersAfterLower,monstersBeforeUpper,monstersAfterUpper;
std::vector<IBullet*>bulletsLower,bulletsUpper;
@ -386,6 +382,8 @@ public:
void PlayFootstepSound();
const std::map<std::string,TilesetData>&GetTilesets()const;
void AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData);
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.
struct TileGroupData{
vi2d tilePos;

@ -166,4 +166,8 @@ const ItemInfo*ItemDrop::GetItem()const{
const std::vector<ItemDrop>&ItemDrop::GetDrops(){
return ItemDrop::drops;
}
void ItemDrop::ClearDrops(){
ItemDrop::drops.clear();
}

@ -45,9 +45,6 @@ namespace MonsterTests{
class ItemDrop{
friend class AiL;
friend class MonsterTests::MonsterTest;
friend class PlayerTests::PlayerTest;
friend class ItemTests::ItemTest;
vf2d pos;
vf2d speed{};
float zSpeed=0;
@ -66,6 +63,7 @@ public:
void Draw()const;
static void UpdateDrops(float fElapsedTime);
static const std::vector<ItemDrop>&GetDrops();
static void ClearDrops();
float GetZ()const;
static void SpawnItem(const ItemInfo*item,vf2d pos,bool isUpper);
const ItemInfo*GetItem()const;

@ -155,7 +155,17 @@ void ItemEnchantInfo::Initialize(){
}
ItemEnchant::ItemEnchant(const std::string_view enchantName)
:enchantName(std::string(enchantName)){}
:enchantName(std::string(enchantName)){
for(const auto&[attr,val]:ItemEnchantInfo::ENCHANT_LIST.at(this->enchantName).minStatModifiers){
float minVal=ItemEnchantInfo::ENCHANT_LIST.at(this->enchantName).minStatModifiers.A_Read(attr);
float maxVal=ItemEnchantInfo::ENCHANT_LIST.at(this->enchantName).maxStatModifiers.A_Read(attr);
if(minVal==maxVal)A(attr)=minVal;
else{
const auto&randRange{std::ranges::iota_view(int(minVal),int(maxVal))};
A(attr)=randRange[util::random()%randRange.size()];
}
}
}
const ItemEnchantInfo&ItemEnchantInfo::GetEnchant(const std::string_view enchantName){
return ENCHANT_LIST.at(std::string(enchantName));

@ -1300,6 +1300,7 @@ 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);
}
}

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

@ -937,8 +937,6 @@ namespace olc
class PixelGameEngine
{
friend class ViewPort;
friend class PlayerTests::PlayerTest;
friend class ItemTests::ItemTest;
struct StringDecalData{
char c;
vf2d sourcePos;
@ -1205,6 +1203,8 @@ namespace olc
const uint8_t GetMosaicEffect()const;
void SetMosaicEffect(uint8_t effectLevel);
HWButton*const GetKeyboardState(uint8_t key);
public:
static std::map<char,Pixel> charToColor;
static std::string Grey;
@ -4396,6 +4396,9 @@ namespace olc
void PixelGameEngine::SetMosaicEffect(uint8_t effectLevel){
mosaic=std::max(uint8_t(1),effectLevel);
}
HWButton*const PixelGameEngine::GetKeyboardState(uint8_t key){
return &pKeyboardState[key];
}
void PixelGameEngine::UpdateTextEntry()
{

@ -6,7 +6,7 @@ ItemConfiguration
Item Categories = ItemCategory.txt
Equipment = Equipment-test.txt
Weapons = Weapons.txt
Accessories = Accessories.txt
Accessories = Accessories-test.txt
}
Item
{

Loading…
Cancel
Save