Add Heal function for monsters. Make player stat functions const return. Add Missing Health bonus stat from buffs. Refactor names of stat functions to be more explicit. Add more stat buff and pct buff checks. Fix up discrepancies with how stat up buffs work for the player. 53/53 passing tests.

mac-build
sigonasr2 7 months ago
parent 02523d5ebb
commit 34515cec76
  1. 14
      Adventures in Lestoria Tests/MonsterTests.cpp
  2. 146
      Adventures in Lestoria Tests/PlayerTests.cpp
  3. 1
      Adventures in Lestoria/AdventuresInLestoria.h
  4. 12
      Adventures in Lestoria/AttributableStat.cpp
  5. 2
      Adventures in Lestoria/AttributableStat.h
  6. 4
      Adventures in Lestoria/CharacterMenuWindow.cpp
  7. 2
      Adventures in Lestoria/DamageNumber.cpp
  8. 1
      Adventures in Lestoria/ItemDrop.h
  9. 16
      Adventures in Lestoria/Monster.cpp
  10. 6
      Adventures in Lestoria/Monster.h
  11. 82
      Adventures in Lestoria/Player.cpp
  12. 24
      Adventures in Lestoria/Player.h
  13. 1
      Adventures in Lestoria/Tutorial.h
  14. 2
      Adventures in Lestoria/Version.h

@ -39,6 +39,7 @@ All rights reserved.
#include "AdventuresInLestoria.h"
#include "config.h"
#include "ItemDrop.h"
#include "Tutorial.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -64,7 +65,14 @@ namespace MonsterTests
testGame->olc_SetTestingMode(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();
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
@ -133,13 +141,13 @@ namespace MonsterTests
}
TEST_METHOD(AttackUpModifierWorks){
Monster buffMonster{{},MONSTER_DATA["TestName"]};
buffMonster.AddBuff(BuffType::STAT_UP,5,5.f,{ItemAttribute::Get("Attack")});
buffMonster.AddBuff(BuffType::STAT_UP,5,5.f,{"Attack"});
Monster testMonster2{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
testMonster2.Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
Assert::AreEqual(game->GetPlayer()->GetMaxHealth()-15,game->GetPlayer()->GetHealth());
Assert::AreEqual(testMonster2.GetMaxHealth()-15,testMonster2.GetHealth());
Assert::AreEqual(game->GetPlayer()->GetMaxHealth()-15,game->GetPlayer()->GetHealth(),L"Player Health is now 85.");
Assert::AreEqual(testMonster2.GetMaxHealth()-15,testMonster2.GetHealth(),L"Monster Health is now 15.");
}
TEST_METHOD(MonsterIsConsideredDeadAt0Health){
Monster testMonster{{},MONSTER_DATA["TestName"]};

@ -40,6 +40,7 @@ All rights reserved.
#include "Tutorial.h"
#include <random>
#include <format>
#include "ItemDrop.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -73,6 +74,14 @@ namespace PlayerTests
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
#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;
#pragma endregion
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
@ -169,9 +178,9 @@ namespace PlayerTests
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100 defense, 1000 health, and 100 attack.
Assert::AreEqual(100.f+player->GetBaseStat("Defense"),player->GetStat("Defense"),L"Defense stat was not increased by 100.");
Assert::AreEqual(100.f+player->GetBaseStat("Attack"),player->GetStat("Attack"),L"Attack stat was not increased by 100.");
Assert::AreEqual(1000.f+player->GetBaseStat("Health"),player->GetStat("Health"),L"Health Points was not increased by 1000.");
Assert::AreEqual(100.f+player->GetBaseStat("Defense"),player->GetEquipStat("Defense"),L"Defense stat was not increased by 100.");
Assert::AreEqual(100.f+player->GetBaseStat("Attack"),player->GetEquipStat("Attack"),L"Attack stat was not increased by 100.");
Assert::AreEqual(1000.f+player->GetBaseStat("Health"),player->GetEquipStat("Health"),L"Health Points was not increased by 1000.");
}
TEST_METHOD(PlayerSetEffectsAcknowledged2){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
@ -179,14 +188,14 @@ namespace PlayerTests
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100% defense, 100% Attack, 100% Move Spd, 100% CDR, 100% Crit Rate, 100% Crit Dmg, 100% Health %, 100% HP/6 Recovery
Assert::AreEqual(100.f+player->GetBaseStat("Defense %"),player->GetStat("Defense %"),L"Defense % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Attack %"),player->GetStat("Attack %"),L"Attack % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Move Spd %"),player->GetStat("Move Spd %"),L"Move Spd % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("CDR"),player->GetStat("CDR"),L"CDR stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Crit Rate % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Crit Dmg"),player->GetStat("Crit Dmg"),L"Critical Damage stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Health %"),player->GetStat("Health %"),L"Health Points was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("HP6 Recovery %"),player->GetStat("HP6 Recovery %"),L"HP/6s Recovery stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Defense %"),player->GetEquipStat("Defense %"),L"Defense % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Attack %"),player->GetEquipStat("Attack %"),L"Attack % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Move Spd % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("CDR"),player->GetEquipStat("CDR"),L"CDR stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Critical Damage stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Health %"),player->GetEquipStat("Health %"),L"Health Points was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("HP6 Recovery %"),player->GetEquipStat("HP6 Recovery %"),L"HP/6s Recovery stat was not increased by 100%.");
}
TEST_METHOD(PlayerSetEffectsAcknowledged3){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
@ -194,8 +203,8 @@ namespace PlayerTests
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100% HP/4 Recovery, 100% Damage Reduction
Assert::AreEqual(100.f+player->GetBaseStat("HP4 Recovery %"),player->GetStat("HP4 Recovery %"),L"HP/4s Recovery stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Damage Reduction"),player->GetStat("Damage Reduction"),L"Damage Reduction stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("HP4 Recovery %"),player->GetEquipStat("HP4 Recovery %"),L"HP/4s Recovery stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Damage Reduction"),player->GetEquipStat("Damage Reduction"),L"Damage Reduction stat was not increased by 100%.");
}
TEST_METHOD(PlayerSetEffectsAcknowledged4){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
@ -203,9 +212,9 @@ namespace PlayerTests
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100% HP/s Recovery, 50% Attack Spd, and 100 More Base Mana.
Assert::AreEqual(100.f+player->GetBaseStat("HP Recovery %"),player->GetStat("HP Recovery %"),L"HP Recovery % stat was not increased by 100%.");
Assert::AreEqual(50.f+player->GetBaseStat("Attack Spd"),player->GetStat("Attack Spd"),L"Attack Speed stat was not increased by 50%.");
Assert::AreEqual(100.f+player->GetBaseStat("Mana"),player->GetStat("Mana"),L"Mana stat was not increased by 100.");
Assert::AreEqual(100.f+player->GetBaseStat("HP Recovery %"),player->GetEquipStat("HP Recovery %"),L"HP Recovery % stat was not increased by 100%.");
Assert::AreEqual(50.f+player->GetBaseStat("Attack Spd"),player->GetEquipStat("Attack Spd"),L"Attack Speed stat was not increased by 50%.");
Assert::AreEqual(100.f+player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Mana stat was not increased by 100.");
}
TEST_METHOD(MultiPieceSetEffectsWork){
std::weak_ptr<Item>testArmor{Inventory::AddItem("Bone Armor"s)};
@ -213,24 +222,24 @@ namespace PlayerTests
std::weak_ptr<Item>testLeggings{Inventory::AddItem("Bone Pants"s)};
std::weak_ptr<Item>testGloves{Inventory::AddItem("Bone Gloves"s)};
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Base Crit Rate does not match initial Crit Rate");
Assert::AreEqual(player->GetBaseStat("Crit Dmg"),player->GetStat("Crit Dmg"),L"Base Crit Dmg does not match initial Crit Dmg");
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Base Crit Rate does not match initial Crit Rate");
Assert::AreEqual(player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Base Crit Dmg does not match initial Crit Dmg");
Inventory::EquipItem(testArmor,EquipSlot::ARMOR);
Inventory::EquipItem(testHeadpiece,EquipSlot::HELMET);
Assert::AreEqual(5+player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Crit Rate does not increase by 5% with 2 Bone Equips (Set Bonus)");
Assert::AreEqual(player->GetBaseStat("Crit Dmg"),player->GetStat("Crit Dmg"),L"Crit Dmg does not remain the same with 2 Bone Equips (Set Bonus)");
Assert::AreEqual(5+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate does not increase by 5% with 2 Bone Equips (Set Bonus)");
Assert::AreEqual(player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Crit Dmg does not remain the same with 2 Bone Equips (Set Bonus)");
Inventory::UnequipItem(EquipSlot::ARMOR);
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Removing an armor piece does not remove the crit rate set effect");
Assert::AreEqual(player->GetBaseStat("Crit Dmg"),player->GetStat("Crit Dmg"),L"Removing an armor piece affects crit dmg incorrectly");
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Removing an armor piece does not remove the crit rate set effect");
Assert::AreEqual(player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Removing an armor piece affects crit dmg incorrectly");
Inventory::EquipItem(testArmor,EquipSlot::ARMOR);
Inventory::EquipItem(testLeggings,EquipSlot::PANTS);
Inventory::EquipItem(testGloves,EquipSlot::GLOVES);
Assert::AreEqual(5+player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Crit Rate does not increase by 5% with 4 Bone Equips (Set Bonus)");
Assert::AreEqual(7+player->GetBaseStat("Crit Dmg"),player->GetStat("Crit Dmg"),L"Crit Dmg does not increase by 5% with 4 Bone Equips (Set Bonus)");
Assert::AreEqual(5+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate does not increase by 5% with 4 Bone Equips (Set Bonus)");
Assert::AreEqual(7+player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Crit Dmg does not increase by 5% with 4 Bone Equips (Set Bonus)");
}
TEST_METHOD(AccessoriesWork){
std::weak_ptr<Item>testRing{Inventory::AddItem("Bird's Treasure"s)};
@ -259,32 +268,91 @@ namespace PlayerTests
testRing2.lock()->RandomStats().A_Read("Mana")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Mana"),std::format(L"Generated Mana {} is out of bounds: Min:{} Max:{}",testRing2.lock()->RandomStats().A_Read("Mana"),ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Mana"),ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Mana")).c_str());
//Ensure stats are default.
Assert::AreEqual(player->GetBaseStat("Move Spd %"),player->GetStat("Move Spd %"),L"Base Move Spd %does not match initial Move Spd %");
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Base Crit Rate does not match initial Crit Rate");
Assert::AreEqual(player->GetBaseStat("Mana"),player->GetStat("Mana"),L"Base Mana does not match initial Mana");
Assert::AreEqual(player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Base Move Spd %does not match initial Move Spd %");
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Base Crit Rate does not match initial Crit Rate");
Assert::AreEqual(player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Base Mana does not match initial Mana");
Inventory::EquipItem(testRing,EquipSlot::RING1);
Assert::AreEqual(3+player->GetBaseStat("Move Spd %"),player->GetStat("Move Spd %"),L"Move Spd % increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Crit Rate increased incorrectly when ring 1 equipped!");
Assert::AreEqual(5+player->GetBaseStat("Mana"),player->GetStat("Mana"),L"Mana increased incorrectly when ring 1 equipped!");
Assert::AreEqual(3+player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Move Spd % increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate increased incorrectly when ring 1 equipped!");
Assert::AreEqual(5+player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Mana increased incorrectly when ring 1 equipped!");
//Equipping to same slot should undo the previous stats and apply the new ring's stats.
Inventory::EquipItem(testRing2,EquipSlot::RING1);
Assert::AreEqual(2+player->GetBaseStat("Move Spd %"),player->GetStat("Move Spd %"),L"Move Spd % increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Crit Rate increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Mana"),player->GetStat("Mana"),L"Mana increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Move Spd % increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Mana increased incorrectly when ring 1 equipped!");
Inventory::UnequipItem(EquipSlot::RING1);
//Ensure stats are default again.
Assert::AreEqual(player->GetBaseStat("Move Spd %"),player->GetStat("Move Spd %"),L"Base Move Spd %does not match initial Move Spd % after unequipping the ring.");
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Base Crit Rate does not match initial Crit Rate after unequipping the ring.");
Assert::AreEqual(player->GetBaseStat("Mana"),player->GetStat("Mana"),L"Base Mana does not match initial Mana after unequipping the ring.");
Assert::AreEqual(player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Base Move Spd %does not match initial Move Spd % after unequipping the ring.");
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Base Crit Rate does not match initial Crit Rate after unequipping the ring.");
Assert::AreEqual(player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Base Mana does not match initial Mana after unequipping the ring.");
Inventory::EquipItem(testRing,EquipSlot::RING1);
Inventory::EquipItem(testRing2,EquipSlot::RING2);
Assert::AreEqual(5+player->GetBaseStat("Move Spd %"),player->GetStat("Move Spd %"),L"Move Spd % increased incorrectly when both rings equipped!");
Assert::AreEqual(4+player->GetBaseStat("Crit Rate"),player->GetStat("Crit Rate"),L"Crit Rate increased incorrectly when both rings equipped!");
Assert::AreEqual(7+player->GetBaseStat("Mana"),player->GetStat("Mana"),L"Mana increased incorrectly when both rings equipped!");
Assert::AreEqual(5+player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Move Spd % increased incorrectly when both rings equipped!");
Assert::AreEqual(4+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate increased incorrectly when both rings equipped!");
Assert::AreEqual(7+player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Mana increased incorrectly when both rings equipped!");
}
TEST_METHOD(FlatDefenseStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
player->Hurt(100,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(17,player->GetHealth(),L"100 Defense prevents 17 damage, resulting in 83 damage points dealt.");
}
TEST_METHOD(HealthStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(HealthStatUpBuffCheck){
player->AddBuff(BuffType::STAT_UP,5.f,1000.f,{"Health"});
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(HealthPctStatUpBuffCheck){
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Health %"});
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(AttackStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100+player->GetBaseStat("Attack"),float(player->GetAttack()),L"Attack stat should be increased from 10 to 110.");
}
TEST_METHOD(DefensePctStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(player->GetBaseStat("Defense"),float(player->GetDefense()),L"100% Defense % causes a 0 increase in Defense when it's default zero.");
std::weak_ptr<Item>bonePants{Inventory::AddItem("Bone Pants"s)};
bonePants.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(bonePants,EquipSlot::PANTS);
Assert::AreEqual(122.f+player->GetBaseStat("Defense"),float(player->GetDefense()),L"100% Defense % causes double increase in Defense. 61 -> 122");
Inventory::UnequipItem(EquipSlot::ARMOR);
player->Hurt(100,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(13,player->GetHealth(),L"Took 87 damage with 61 defense.");
player->Heal(87); //Back to 100 Health.
LOG(std::format("Player Health is now {}",player->GetHealth()));
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
player->Hurt(100,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(36,player->GetHealth(),L"Took 54 damage with 122 defense.");
}
TEST_METHOD(AttackPctStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(player->GetAttack(),player->OnUpperLevel(),player->GetZ());
const int damageDealt{testMonster.GetMaxHealth()-testMonster.GetHealth()};
testMonster.Heal(testMonster.GetMaxHealth());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(player->GetBaseStat("Attack")*2,float(player->GetAttack()),L"100% Attack should double the attack of the player.");
testMonster.Hurt(player->GetAttack(),player->OnUpperLevel(),player->GetZ());
const int enhancedDamageDealt{testMonster.GetMaxHealth()-testMonster.GetHealth()};
Assert::AreEqual(enhancedDamageDealt,damageDealt*5,L"Original damage dealt should be half of the new attack. Factor in 100% crit rate and a bonus 100% crit dmg (making it 250%) 200%*2.5=500% damage.");
}
};
}

@ -93,6 +93,7 @@ class AiL : public olc::PixelGameEngine
friend class sig::Animation;
friend class Audio;
friend class Minimap;
friend class MonsterTests::MonsterTest;
friend class PlayerTests::PlayerTest;
std::unique_ptr<Player>player;
SplashScreen splash;

@ -94,6 +94,12 @@ const bool ItemAttribute::operator<(const ItemAttribute&rhs)const{
return originalName<rhs.originalName;
};
float operator+(const float&lhs,const ItemAttribute&rhs){
float result{lhs};
result+=rhs;
return result;
};
const std::string_view ItemAttribute::Modifies()const{
return modifies;
};
@ -118,10 +124,11 @@ float&operator+=(float&lhs,const ItemAttribute&rhs){
}
#pragma endregion
if(rhs.DisplayAsPercent()){ //This is a percentage-based stat modifier.
statModifierTotal+=stats->A_Read(rhs)/100.f;
if(rhs.Modifies().length()>0){
lhs+=stats->A_Read(rhs.Modifies())*statModifierTotal;
}else{
lhs+=stats->A_Read(rhs)*statModifierTotal;
lhs*=statModifierTotal;
}
}else{ //This is a flat stat modifier.
lhs+=statModifierTotal;
@ -152,10 +159,11 @@ float&operator-=(float&lhs,const ItemAttribute&rhs){
}
#pragma endregion
if(rhs.DisplayAsPercent()){ //This is a percentage-based stat modifier.
statModifierTotal+=stats->A_Read(rhs)/100.f;
if(rhs.Modifies().length()>0){
lhs-=stats->A_Read(rhs.Modifies())*statModifierTotal;
}else{
lhs-=stats->A_Read(rhs)*statModifierTotal;
lhs/=statModifierTotal;
}
}else{ //This is a flat stat modifier.
lhs-=statModifierTotal;

@ -69,6 +69,7 @@ public:
friend float&operator+=(float&lhs,const ItemAttribute&rhs);
friend float&operator-=(float&lhs,const ItemAttribute&rhs);
const bool operator==(const ItemAttribute&rhs)const;
friend float operator+(const float&lhs,const ItemAttribute&rhs);
static void Initialize();
};
@ -76,6 +77,7 @@ struct Stats;
class ItemAttributable{
friend class ItemInfo;
friend class EntityStats;
protected:
std::map<ItemAttribute,float>attributes;
public:

@ -65,7 +65,7 @@ namespace CharacterMenuWindow{
const static std::array<AttributeData,7>displayAttrs{
AttributeData{"Health",[]()->int{return game->GetPlayer()->GetMaxHealth();}},
AttributeData{"Attack",[]()->int{return game->GetPlayer()->GetAttack();}},
AttributeData{"Defense",[]()->int{return game->GetPlayer()->GetStat("Defense");}},
AttributeData{"Defense",[]()->int{return game->GetPlayer()->GetDefense();}},
AttributeData{"Move Spd %",[]()->int{return ceil(game->GetPlayer()->GetMoveSpdMult()*100);}},
AttributeData{"CDR",[]()->int{return ceil(game->GetPlayer()->GetCooldownReductionPct()*100);}},
AttributeData{"Crit Rate",[]()->int{return ceil(game->GetPlayer()->GetCritRatePct()*100);}},
@ -178,7 +178,7 @@ void Menu::InitializeCharacterMenuWindow(){
const static auto GetLabelText=[](ItemAttribute attribute){
std::string attrStr=std::string(attribute.Name())+"\n ";
attrStr+=std::to_string(game->GetPlayer()->GetStat(attribute));
attrStr+=std::to_string(game->GetPlayer()->GetEquipStat(attribute));
if(attribute.DisplayAsPercent()){
attrStr+="%";
}

@ -54,7 +54,7 @@ DamageNumber::DamageNumber(vf2d pos,int damage,bool friendly,DamageNumberType ty
}
void DamageNumber::RecalculateSize(){
float damageMultRatio=damage/game->GetPlayer()->GetStat("Attack")/2.f;
float damageMultRatio=damage/game->GetPlayer()->GetEquipStat("Attack")/2.f;
riseSpd=originalRiseSpd;
if(!friendly){
float newSize=std::clamp(roundf(damageMultRatio),1.0f,4.0f);

@ -46,6 +46,7 @@ namespace MonsterTests{
class ItemDrop{
friend class AiL;
friend class MonsterTests::MonsterTest;
friend class PlayerTests::PlayerTest;
vf2d pos;
vf2d speed{};
float zSpeed=0;

@ -91,8 +91,8 @@ const int Monster::GetMaxHealth()const{
}
int Monster::GetAttack(){
float mod_atk=float(stats.A("Attack"));
mod_atk+=Get("Attack %");
mod_atk+=Get("Attack");
mod_atk+=GetBonusStat("Attack %");
mod_atk+=GetBonusStat("Attack");
return int(mod_atk);
}
float Monster::GetMoveSpdMult(){
@ -723,6 +723,10 @@ void Monster::AddBuff(BuffType type,float duration,float intensity,std::set<Item
buffList.push_back(Buff{type,duration,intensity,attr});
}
void Monster::AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr){
buffList.push_back(Buff{type,duration,intensity,attr});
}
void Monster::RemoveBuff(BuffType type){
std::erase_if(buffList,[&](const Buff&buff){return buff.type==type;});
}
@ -909,8 +913,8 @@ const ItemAttributable&Monster::GetStats()const{
return stats;
}
ItemAttribute&Monster::Get(std::string_view attr){
return ItemAttribute::Get(attr,this);
const ItemAttribute&Monster::GetBonusStat(std::string_view attr)const{
return ItemAttribute::Get(attr,const_cast<Monster*>(this));
}
const uint32_t MonsterData::GetXP()const{
@ -1146,3 +1150,7 @@ const bool Monster::IsSolid()const{
void Monster::_DealTrueDamage(const uint32_t damageAmt){
_Hurt(damageAmt,OnUpperLevel(),GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES);
}
void Monster::Heal(const int healAmt){
hp=std::clamp(hp+healAmt,0,int(GetMaxHealth()));
}

@ -137,7 +137,10 @@ public:
bool StartPathfinding(float pathingTime);
void PathAroundBehavior(float fElapsedTime);
void AddBuff(BuffType type,float duration,float intensity);
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
void AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr);
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
void AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr);
std::vector<Buff>GetBuffs(BuffType buff)const;
//Removes all buffs of a given type.
void RemoveBuff(BuffType type);
@ -193,6 +196,7 @@ public:
const bool ReachedTargetPos(const float maxDistanceFromTarget=4.f)const;
const float GetHealthRatio()const;
void _DealTrueDamage(const uint32_t damageAmt);
void Heal(const int healAmt);
private:
//NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!!
// The way this works is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop.
@ -246,7 +250,7 @@ private:
std::function<bool(GameEvent&,Monster&,const std::string&)>strategyDeathFunc{};
void SetStrategyDeathFunction(std::function<bool(GameEvent&,Monster&,const std::string&)>func);
//If you are trying to change a Get() stat, use the STAT_UP buff (and the optional argument) to supply an attribute you want to apply.
ItemAttribute&Get(std::string_view attr);
const ItemAttribute&GetBonusStat(std::string_view attr)const;
//Returns false if the monster could not be moved to the requested location due to collision.
//If monsterInvoked is true, this means the monster was the one that instantiated this input, and it's not an extra movement done via collision.
//Set monsterInvoked to false when you don't want a movement loop due to collisions.

@ -255,8 +255,7 @@ const int Player::GetHealth()const{
}
const int Player::GetMaxHealth()const{
const float hpPctIncrease=GetStat("Health")*GetStat("Health %")/100.f;
return int(GetStat("Health")+hpPctIncrease);
return int(GetModdedStatBonuses("Health"));
}
const int Player::GetMana()const{
@ -264,20 +263,21 @@ const int Player::GetMana()const{
}
const int Player::GetMaxMana()const{
return GetStat("Mana");
return GetEquipStat("Mana");
}
const int Player::GetAttack(){
float mod_atk=float(GetStat("Attack"));
mod_atk+=Get("Attack");
mod_atk+=Get("Attack %");
return int(mod_atk);
const int Player::GetAttack()const{
return int(GetModdedStatBonuses("Attack"));
}
const int Player::GetDefense()const{
return int(GetModdedStatBonuses("Defense"));
}
float Player::GetMoveSpdMult(){
float moveSpdPct=GetStat("Move Spd %")/100.f;
float moveSpdPct=GetEquipStat("Move Spd %")/100.f;
float mod_moveSpd=moveSpdPct;
mod_moveSpd+=ItemAttribute::Get("Move Spd %",this);
mod_moveSpd+=GetBonusStat("Move Spd %");
for(const Buff&b:GetBuffs(BuffType::SLOWDOWN)){
mod_moveSpd-=moveSpdPct*b.intensity;
}
@ -759,7 +759,7 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z){
finalPctDmgTaken=std::max(6.25_Pct,finalPctDmgTaken);//Apply Damage Cap.
float minPctDmgReduction=0.05_Pct*GetStat("Defense");
float minPctDmgReduction=0.05_Pct*GetDefense();
float finalPctDmgReduction=1-finalPctDmgTaken;
float pctDmgReductionDiff=finalPctDmgReduction-minPctDmgReduction;
@ -1129,12 +1129,12 @@ void Player::SetItem3UseFunc(Ability a){
useItem3=a;
};
const float&Player::GetStat(std::string_view a)const{
return GetStat(ItemAttribute::Get(a));
const float&Player::GetEquipStat(std::string_view a)const{
return GetEquipStat(ItemAttribute::Get(a));
}
const float&Player::GetStat(ItemAttribute a)const{
return stats.GetStat(a);
const float&Player::GetEquipStat(ItemAttribute a)const{
return stats.GetEquipStat(a);
}
const float&Player::GetBaseStat(std::string_view a)const{
@ -1172,11 +1172,11 @@ void EntityStats::RecalculateEquipStats(){
}
}
const float&EntityStats::GetStat(ItemAttribute stat)const{
const float&EntityStats::GetEquipStat(ItemAttribute stat)const{
return equipStats.A_Read(stat);
}
const float&EntityStats::GetStat(std::string_view stat)const{
const float&EntityStats::GetEquipStat(std::string_view stat)const{
return equipStats.A_Read(stat);
}
@ -1233,7 +1233,7 @@ const float Player::GetDamageReductionFromBuffs()const{
const float Player::GetDamageReductionFromArmor()const{
float dmgReduction=0;
dmgReduction+=Stats::GetDamageReductionPct(GetStat("Defense"));
dmgReduction+=Stats::GetDamageReductionPct(GetDefense());
return std::min(0.75f,dmgReduction);
};
@ -1265,8 +1265,8 @@ const ItemAttributable&Player::GetBaseStats()const{
return stats.GetBaseStats();
};
ItemAttribute&Player::Get(std::string_view attr){
return ItemAttribute::Get(attr,this);
const ItemAttribute&Player::GetBonusStat(std::string_view attr)const{
return ItemAttribute::Get(attr,const_cast<Player*>(this));
}
const float Player::GetCooldownReductionPct()const{
@ -1275,34 +1275,34 @@ const float Player::GetCooldownReductionPct()const{
for(const Buff&b:buffs){
modCDRPct+=b.intensity;
}
modCDRPct+=GetStat("CDR")/100;
modCDRPct+=GetEquipStat("CDR")/100;
return modCDRPct;
}
const float Player::GetCritRatePct()const{
float modCritRatePct=0;
modCritRatePct+=GetStat("Crit Rate")/100;
modCritRatePct+=GetEquipStat("Crit Rate")/100;
return modCritRatePct;
}
const float Player::GetCritDmgPct()const{
float modCritDmgPct=0;
modCritDmgPct+=GetStat("Crit Dmg")/100;
modCritDmgPct+=GetEquipStat("Crit Dmg")/100;
return modCritDmgPct;
}
const float Player::GetHPRecoveryPct()const{
float modHPRecoveryPct=0;
modHPRecoveryPct+=GetStat("HP Recovery %")/100;
modHPRecoveryPct+=GetEquipStat("HP Recovery %")/100;
return modHPRecoveryPct;
}
const float Player::GetHP6RecoveryPct()const{
float modHPRecoveryPct=0;
modHPRecoveryPct+=GetStat("HP6 Recovery %")/100;
modHPRecoveryPct+=GetEquipStat("HP6 Recovery %")/100;
return modHPRecoveryPct;
}
const float Player::GetHP4RecoveryPct()const{
float modHPRecoveryPct=0;
modHPRecoveryPct+=GetStat("HP4 Recovery %")/100;
modHPRecoveryPct+=GetEquipStat("HP4 Recovery %")/100;
return modHPRecoveryPct;
}
@ -1337,7 +1337,7 @@ void Player::PerformHPRecovery(){
const float Player::GetDamageReductionPct()const{
float modDmgReductionPct=0;
modDmgReductionPct+=GetStat("Damage Reduction")/100;
modDmgReductionPct+=GetEquipStat("Damage Reduction")/100;
return modDmgReductionPct;
}
@ -1418,7 +1418,7 @@ const uint32_t Player::GetAccumulatedXP()const{
}
const float Player::GetAttackRecoveryRateReduction()const{
return GetStat("Attack Spd");
return GetEquipStat("Attack Spd");
}
void EntityStats::Reset(){
@ -1601,4 +1601,30 @@ void Player::CheckAndPerformAbility(Ability&ability,InputGroup key){
}else
if(key.Released()||!key.Held())ability.waitForRelease=false;
}
}
const float Player::GetModdedStatBonuses(std::string_view stat)const{
if(ItemAttribute::Get(stat).DisplayAsPercent())ERR(std::format("WARNING! Stat {} was provided. A percentage-based stat should not be supplied here! GetModdedStatBonuses() is supposed to calculate using a BASE stat and includes the percentage modifier already! Please fix this!",stat))
float flatBonuses{GetEquipStat(stat)+GetBonusStat(stat)};
float pctBonusSum{};
for(const auto&[attrName,attr]:ItemAttribute::attributes){
if(stat==attrName)continue; //Ignore self.
if(attr.Modifies()==stat){
pctBonusSum+=stats.GetStats().A_Read(attrName);
}
}
for(const Buff&buff:GetBuffs(BuffType::STAT_UP)){
for(const ItemAttribute&attr:buff.attr){
if(attr.Modifies()==stat){
if(attr.DisplayAsPercent()){
pctBonusSum+=buff.intensity*100.f;
}
}
}
}
return flatBonuses+flatBonuses*pctBonusSum/100.f;
}
const float Player::GetModdedStatBonuses(ItemAttribute stat)const{
return GetModdedStatBonuses(stat.ActualName());
}

@ -81,8 +81,10 @@ public:
void RecalculateEquipStats(); //Called when equipment is updated.
const ItemAttributable&GetStats()const;
const ItemAttributable&GetBaseStats()const;
const float&GetStat(ItemAttribute stat)const; //Get stats with equipment applied.
const float&GetStat(std::string_view stat)const; //Get stats with equipment applied.
//Get stats with equipment applied.
const float&GetEquipStat(ItemAttribute stat)const;
//Get stats with equipment applied.
const float&GetEquipStat(std::string_view stat)const;
const float&GetBaseStat(ItemAttribute stat)const;
const float&GetBaseStat(std::string_view stat)const;
void SetBaseStat(ItemAttribute a,float val);
@ -114,8 +116,8 @@ public:
float GetX();
float GetY();
float GetZ();
const float&GetStat(ItemAttribute a)const;
const float&GetStat(std::string_view a)const;
const float&GetEquipStat(ItemAttribute a)const;
const float&GetEquipStat(std::string_view a)const;
const float&GetBaseStat(ItemAttribute a)const;
const float&GetBaseStat(std::string_view a)const;
void SetBaseStat(std::string_view a,float val);
@ -124,7 +126,8 @@ public:
const int GetHealth()const;
const int GetMana()const;
const int GetMaxMana()const;
const int GetAttack();
const int GetAttack()const;
const int GetDefense()const;
const float GetDamageReductionFromBuffs()const;
const float GetDamageReductionFromArmor()const;
float GetMoveSpdMult();
@ -171,7 +174,9 @@ public:
void SetState(State::State newState);
void AddBuff(BuffType type,float duration,float intensity);
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
void AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr);
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
void AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr);
void AddBuff(BuffType type,float duration,float intensity,float timeBetweenTicks,std::function<void(AiL*,int)>repeatAction);
@ -271,6 +276,10 @@ public:
void AddVelocity(vf2d vel);
const float GetHealthRatio()const;
const bool IsAlive()const;
//An all-in-one function that applies a stat plus any % modifiers the stat may have as well.
const float GetModdedStatBonuses(std::string_view stat)const;
//An all-in-one function that applies a stat plus any % modifiers the stat may have as well.
const float GetModdedStatBonuses(ItemAttribute stat)const;
private:
int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I;
@ -318,7 +327,10 @@ private:
Ability useItem3;
uint32_t money="Player.Starting Money"_I;
EntityStats stats;
ItemAttribute&Get(std::string_view attr);
//So this is kind of a stupid one...
//If the attr provided is a % modifier, it will take the lhs in a += operation and multiply it by the % modifier from player's actual stats.
//If the attr provided is not a % modifier, it will add in a +=/+ operation...
const ItemAttribute&GetBonusStat(std::string_view attr)const;
bool rendered=false;
bool invisibility=false;
//Returns true if the move was valid and successful.

@ -109,7 +109,6 @@ public:
inline SetLoadoutItemTask():TutorialTask(){};
private:
virtual inline void Initialize()override final{
if(game->TestingModeEnabled())return;
Component<MenuComponent>(ITEM_LOADOUT,"Start Level Button")->SetGrayedOut(true);
}
virtual inline bool CompleteCondition()override final{

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

Loading…
Cancel
Save