Add Enchant API and EnchantItem() function that applies a random Enchant onto an item. Added HasEnchant() check to see if an equip has an enchant for future integration of enchants in the game. Fix money listeners structure not being reset between unit tests. Add unit tests to ensure items receive correct valid enchants and we can properly detect the player has enchants. Release Build 10527.

mac-build
sigonasr2 6 months ago
parent a295a8e309
commit 68e7446053
  1. 17
      Adventures in Lestoria Tests/ItemTests.cpp
  2. 48
      Adventures in Lestoria Tests/PlayerTests.cpp
  3. 1
      Adventures in Lestoria/AdventuresInLestoria.cpp
  4. 13
      Adventures in Lestoria/Item.cpp
  5. 5
      Adventures in Lestoria/Item.h
  6. 48
      Adventures in Lestoria/ItemEnchant.cpp
  7. 27
      Adventures in Lestoria/ItemEnchant.h
  8. 10
      Adventures in Lestoria/Player.cpp
  9. 2
      Adventures in Lestoria/Player.h
  10. 2
      Adventures in Lestoria/Version.h
  11. 2
      Adventures in Lestoria/assets/config/items/ItemEnchants.txt
  12. BIN
      x64/Release/Adventures in Lestoria.exe
  13. 1
      x64/Unit Testing/assets/config/items/items-test.txt

@ -41,6 +41,7 @@ All rights reserved.
#include <random>
#include <format>
#include "ItemDrop.h"
#include <ranges>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -226,5 +227,21 @@ namespace ItemTests
Assert::AreEqual(ITEM_DATA[slimeKingRing.lock()->ActualName()].GetMaxStats().A_Read(attr),val,L"The current stats should be equal to the maximum stats when refinement is done.");
}
}
TEST_METHOD(EnchantTestCheck){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
Assert::IsFalse(slimeKingRing.lock()->HasEnchant());
for(int i:std::ranges::iota_view(0,1000)){
slimeKingRing.lock()->EnchantItem(ItemEnchant::RollRandomEnchant());
Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability.
}
testGame->ChangePlayerClass(WIZARD);
player=testGame->GetPlayer(); //The player pointer has been reassigned...
for(int i:std::ranges::iota_view(0,1000)){
slimeKingRing.lock()->EnchantItem(ItemEnchant::RollRandomEnchant());
Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability.
}
}
};
}

@ -605,5 +605,51 @@ namespace PlayerTests
})
,L"There should be 2 damage numbers of type DOT.");
}
TEST_METHOD(HasEnchantCheck){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->EnchantItem("Emergency Recovery");
Assert::IsTrue(player->HasEnchant("Emergency Recovery"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Emergency Recovery"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->EnchantItem("Reaper of Souls");
Assert::IsTrue(player->HasEnchant("Reaper of Souls"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Reaper of Souls"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->EnchantItem("Attack Boost");
Assert::IsTrue(player->HasEnchant("Attack Boost"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
std::weak_ptr<Item>leatherHelmet{Inventory::AddItem("Leather Helmet"s)};
std::weak_ptr<Item>leatherArmor{Inventory::AddItem("Leather Armor"s)};
std::weak_ptr<Item>leatherPants{Inventory::AddItem("Leather Pants"s)};
std::weak_ptr<Item>leatherGloves{Inventory::AddItem("Leather Gloves"s)};
std::weak_ptr<Item>leatherShoes{Inventory::AddItem("Leather Shoes"s)};
std::weak_ptr<Item>woodenSword{Inventory::AddItem("Wooden Sword"s)};
Inventory::EquipItem(leatherHelmet,EquipSlot::HELMET);
Inventory::EquipItem(leatherArmor,EquipSlot::ARMOR);
Inventory::EquipItem(leatherPants,EquipSlot::PANTS);
Inventory::EquipItem(leatherGloves,EquipSlot::GLOVES);
Inventory::EquipItem(leatherShoes,EquipSlot::SHOES);
Inventory::EquipItem(woodenSword,EquipSlot::WEAPON);
leatherHelmet.lock()->EnchantItem("Wizard's Soul");
Assert::IsTrue(player->HasEnchant("Wizard's Soul"));
leatherArmor.lock()->EnchantItem("Ability Haste");
Assert::IsTrue(player->HasEnchant("Ability Haste"));
leatherPants.lock()->EnchantItem("Improved Ground Slam");
Assert::IsTrue(player->HasEnchant("Improved Ground Slam"));
leatherGloves.lock()->EnchantItem("Battle Shout");
Assert::IsTrue(player->HasEnchant("Battle Shout"));
leatherShoes.lock()->EnchantItem("Attack Boost");
Inventory::UnequipItem(EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
woodenSword.lock()->EnchantItem("Mana Pool");
Assert::IsTrue(player->HasEnchant("Mana Pool"));
}
};
}
}

@ -4416,6 +4416,7 @@ void AiL::UsingSteamAPI(const bool usingSteam){
void AiL::InitializePlayer(){
player=std::make_unique<Warrior>();
Player::moneyListeners.clear();
}
void AiL::SetWorldZoom(float newZoomScale){

@ -1440,4 +1440,17 @@ RefineResult Item::Refine(){
randomizedStats.A(chosenAttr)=std::min(maxStat,oldAmt+increaseAmt);
const float newAmt{randomizedStats.A_Read(chosenAttr)};
return RefineResult{chosenAttr,newAmt-oldAmt};
}
void Item::EnchantItem(const std::string_view enchantName){
enchant=ItemEnchant{enchantName};
game->GetPlayer()->RecalculateEquipStats();
}
std::optional<ItemEnchant>Item::GetEnchant()const{
return enchant;
}
const bool Item::HasEnchant()const{
return enchant.has_value();
}

@ -49,6 +49,7 @@ All rights reserved.
#include "IT.h"
#include <unordered_set>
#include "Buff.h"
#include "ItemEnchant.h"
class AiL;
class ItemInfo;
@ -186,6 +187,7 @@ private:
ItemInfo*it;
Stats randomizedStats;
bool locked=false;
std::optional<ItemEnchant>enchant;
void SetAmt(uint32_t newAmt);
static ItemEnhancementFunctionPrimingData enhanceFunctionPrimed;
@ -247,6 +249,9 @@ public:
RefineResult Refine();
void Lock();
void Unlock();
void EnchantItem(const std::string_view enchantName);
std::optional<ItemEnchant>GetEnchant()const;
const bool HasEnchant()const;
friend const bool operator==(std::shared_ptr<Item>lhs,std::shared_ptr<Item>rhs){return lhs->it==rhs->it&&lhs->randomizedStats==rhs->randomizedStats;};
friend const bool operator==(std::shared_ptr<Item>lhs,const IT&rhs){return lhs->ActualName()==const_cast<IT&>(rhs);};
friend const bool operator==(std::weak_ptr<Item>lhs,std::weak_ptr<Item>rhs){return !lhs.expired()&&!rhs.expired()&&lhs.lock()->it==rhs.lock()->it&&lhs.lock()->randomizedStats==rhs.lock()->randomizedStats;};

@ -42,7 +42,10 @@ All rights reserved.
#include "Class.h"
#include <unordered_set>
#include <algorithm>
#include "util.h"
#include "AdventuresInLestoria.h"
INCLUDE_game
INCLUDE_DATA
std::unordered_map<std::string,ItemEnchantInfo>ItemEnchantInfo::ENCHANT_LIST;
@ -136,6 +139,7 @@ void ItemEnchantInfo::Initialize(){
datafile&classEnchantData{enchantData[key]};
for(const auto&[key,size]:classEnchantData){
ItemEnchantInfo newEnchant{MakeEnchant(key,classEnchantData[key])};
newEnchant.abilityClass=itemEnchantClass;
const auto&result{ENCHANT_LIST.insert({key,newEnchant})};
const bool InsertFailed{!result.second};
if(InsertFailed)ERR(std::format("WARNING! Enchant {} already existed in Enchant List! Duplicates are not allowed!",key));
@ -151,7 +155,7 @@ void ItemEnchantInfo::Initialize(){
}
ItemEnchant::ItemEnchant(const std::string_view enchantName)
:enchant(ItemEnchantInfo::GetEnchant(enchantName)){}
:enchantName(std::string(enchantName)){}
const ItemEnchantInfo&ItemEnchantInfo::GetEnchant(const std::string_view enchantName){
return ENCHANT_LIST.at(std::string(enchantName));
@ -174,4 +178,46 @@ const std::string_view ItemEnchantInfo::Description()const{
}
const float ItemEnchantInfo::GetConfigValue(const std::string_view keyName)const{
return config.at(std::string(keyName));
}
const std::optional<Class>&ItemEnchantInfo::GetClass()const{
return abilityClass;
}
const ItemEnchantInfo::ItemEnchantCategory&ItemEnchantInfo::Category()const{
return category;
}
const ItemEnchantInfo&ItemEnchant::GetEnchantInfo()const{
return ItemEnchantInfo::ENCHANT_LIST.at(enchantName);
}
const std::string ItemEnchant::Name(ItemEnchantInfo::TextStyle style)const{
return GetEnchantInfo().Name(style);
}
const std::string_view ItemEnchant::Description()const{
return GetEnchantInfo().Description();
}
const ItemEnchantInfo::ItemEnchantCategory&ItemEnchant::Category()const{
return GetEnchantInfo().Category();
}
const std::optional<ItemEnchantInfo::AbilitySlot>&ItemEnchant::AbilitySlot()const{
return GetEnchantInfo().abilitySlot;
}
const std::unordered_map<std::string,ItemEnchantInfo>&ItemEnchantInfo::GetEnchants(){
return ENCHANT_LIST;
}
const std::string ItemEnchant::RollRandomEnchant(){
std::vector<ItemEnchantInfo>filteredEnchants;
std::for_each(ItemEnchantInfo::GetEnchants().begin(),ItemEnchantInfo::GetEnchants().end(),[&filteredEnchants](const std::pair<std::string,ItemEnchantInfo>&data){
using enum ItemEnchantInfo::ItemEnchantCategory;
const ItemEnchantInfo&enchant=data.second;
const auto enchantAvailableForCurrentClass=enchant.Category()!=CLASS||enchant.GetClass().has_value()&&enchant.GetClass().value()==game->GetPlayer()->GetClass();
if(enchantAvailableForCurrentClass){
filteredEnchants.emplace_back(enchant);
}
});
return filteredEnchants[util::random()%filteredEnchants.size()].Name();
}

@ -39,19 +39,31 @@ All rights reserved.
#include "AttributableStat.h"
#include "Pixel.h"
#include "Class.h"
class ItemEnchantInfo{
friend class ItemEnchant;
friend class ItemTests::ItemTest;
friend class PlayerTests::PlayerTest;
public:
enum class TextStyle{
NORMAL,
COLOR_CODES,
};
enum class ItemEnchantCategory{
GENERAL,
CLASS,
UNIQUE,
};
static void Initialize();
const static ItemEnchantInfo&GetEnchant(const std::string_view enchantName);
const static std::unordered_map<std::string,ItemEnchantInfo>&GetEnchants();
const std::string Name(TextStyle style=TextStyle::NORMAL)const;
const std::string_view Description()const;
const ItemEnchantCategory&Category()const;
const std::optional<Class>&GetClass()const;
const float GetConfigValue(const std::string_view keyName)const;
enum class AbilitySlot{
AUTO_ATTACK,
@ -61,11 +73,6 @@ public:
ABILITY_3,
};
private:
enum class ItemEnchantCategory{
GENERAL,
CLASS,
UNIQUE,
};
class ItemEnchantCategoryData{
friend class ItemEnchantInfo;
using LowestNumber=int;
@ -76,6 +83,7 @@ private:
ItemEnchantCategory category;
std::string name;
std::string description;
std::optional<Class>abilityClass;
std::optional<AbilitySlot>abilitySlot;
ItemAttributable minStatModifiers;
ItemAttributable maxStatModifiers;
@ -87,6 +95,13 @@ private:
class ItemEnchant:public ItemAttributable{
public:
ItemEnchant(const std::string_view enchantName);
const std::string Name(ItemEnchantInfo::TextStyle style=ItemEnchantInfo::TextStyle::NORMAL)const;
const std::string_view Description()const;
const ItemEnchantInfo::ItemEnchantCategory&Category()const;
const std::optional<ItemEnchantInfo::AbilitySlot>&AbilitySlot()const;
//Rolls a class-appropriate random enchant.
const static std::string RollRandomEnchant();
private:
const ItemEnchantInfo&enchant;
const ItemEnchantInfo&GetEnchantInfo()const;
std::string enchantName;
};

@ -1384,6 +1384,12 @@ const float Player::GetDamageReductionFromArmor()const{
void Player::RecalculateEquipStats(){
stats.RecalculateEquipStats();
enchantList.clear();
const auto EquipSlotHasEnchant=[](const EquipSlot slot,const std::string_view enchantToCheck){return !Inventory::GetEquip(slot).expired()&&Inventory::GetEquip(slot).lock()->GetEnchant().has_value()&&Inventory::GetEquip(slot).lock()->GetEnchant().value().Name()==enchantToCheck;};
for(int i=int(EquipSlot::RING2);i>=int(EquipSlot::HELMET);i>>=1){ //I'm inverting the order of this equip slot check because typically the player will have enchants in the ring slots, so check those earlier to terminate the loop quicker.
EquipSlot slot=EquipSlot(i);
if(!ISBLANK(Inventory::GetEquip(slot))&&Inventory::GetEquip(slot).lock()->GetEnchant().has_value())enchantList.insert(Inventory::GetEquip(slot).lock()->GetEnchant().value().Name());
}
}
const std::vector<Buff>Player::GetStatBuffs(const std::vector<std::string>&attr)const{
@ -1847,4 +1853,8 @@ const float Player::GetDamageAmplificationMult()const{
damageAmpMult+=buff.intensity;
}
return damageAmpMult;
}
const bool Player::HasEnchant(const std::string_view enchantName)const{
return enchantList.count(std::string(enchantName));
}

@ -293,6 +293,7 @@ public:
const std::unordered_set<std::string>&GetMyClass()const;
void ApplyDot(float duration,int damage,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc=[](Player*p,Buff&b){});
const float GetDamageAmplificationMult()const;
const bool HasEnchant(const std::string_view enchantName)const;
private:
int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I;
@ -370,6 +371,7 @@ private:
float deadlyDashAdditiveBlendingToggleTimer{};
std::unordered_set<std::string>myClass{};
float daggerThrowWaitTimer{INFINITY};
std::unordered_set<std::string>enchantList;
protected:
const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F;
const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F;

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

@ -623,7 +623,7 @@ Item Enchants
# Stat, Lowest, Highest Value
# Stat Modifier[0] = ..., 0, 0
}
Wizards Soul
Wizard's Soul
{
Description = "Using an ability reduces the cooldown of every other ability by {ABILITY COOLDOWN AMOUNT} seconds. Mana Recovery also increases by {MANA RECOVERY MULT}x for {MANA RECOVERY DURATION} seconds."

@ -1,5 +1,6 @@
ItemConfiguration
{
Item Enchants = ItemEnchants.txt
Item Database = ItemDatabase-test.txt
Item Scripts = ItemScript.txt
Item Categories = ItemCategory.txt

Loading…
Cancel
Save