Implement new enchant rolling rule: Same enchant may be chosen if the result ends up increasing at least of the supported stats. Release Build 11592.

master
sigonasr2 2 months ago
parent 880b8e4335
commit 0f0a70b2b4
  1. 40
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 40
      Adventures in Lestoria Tests/ItemTests.cpp
  3. 20
      Adventures in Lestoria Tests/PlayerTests.cpp
  4. 2
      Adventures in Lestoria/AdventuresInLestoria.cpp
  5. 5
      Adventures in Lestoria/ArtificerEnchantConfirmWindow.cpp
  6. 4
      Adventures in Lestoria/ArtificerEnchantWindow.cpp
  7. 26
      Adventures in Lestoria/Attributable.h
  8. 3
      Adventures in Lestoria/AttributableStat.h
  9. 18
      Adventures in Lestoria/Item.cpp
  10. 5
      Adventures in Lestoria/Item.h
  11. 32
      Adventures in Lestoria/ItemEnchant.cpp
  12. 4
      Adventures in Lestoria/ItemEnchant.h
  13. 3
      Adventures in Lestoria/MonsterAttribute.h
  14. 2
      Adventures in Lestoria/Version.h
  15. BIN
      x64/Release/Adventures in Lestoria.exe

@ -118,13 +118,13 @@ namespace EnchantTests
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Aura of the Beast")};
std::unordered_map<int,uint32_t>statDistribution;
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Health Boost");
nullRing.lock()->_EnchantItem(ItemEnchant{"Health Boost"});
statDistribution[player->GetMaxHealth()]++;
}
Assert::AreEqual(size_t(3),statDistribution.size(),L"There should be three entries generated. If not, then the RNG picking is likely not working!");
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Aura of the Beast",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Health Boost");
nullRing2.lock()->_EnchantItem(ItemEnchant{"Health Boost"});
Test::InRange(player->GetMaxHealth(),{106,110},L"Max Health not in expected range with two rings equipped.");
}
}
@ -133,12 +133,12 @@ namespace EnchantTests
Assert::AreEqual(100,player->GetAttack(),L"Player starts with 100 attack.");
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Attack Boost")};
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Attack Boost");
nullRing.lock()->_EnchantItem(ItemEnchant{"Attack Boost"});
Test::InRange(player->GetAttack(),{103,105},L"Attack not in expected range.");
}
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Attack Boost",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Attack Boost");
nullRing2.lock()->_EnchantItem(ItemEnchant{"Attack Boost"});
Test::InRange(player->GetAttack(),{106,110},L"Attack not in expected range with two rings equipped.");
}
}
@ -146,12 +146,12 @@ namespace EnchantTests
Assert::AreEqual(100.0_Pct,player->GetMoveSpdMult(),L"Player starts with 100% Movespd.");
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Movement Boost")};
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Movement Boost");
nullRing.lock()->_EnchantItem(ItemEnchant{"Movement Boost"});
Test::InRange(player->GetMoveSpdMult(),{103.0_Pct,105.0_Pct},L"Move Speed not in expected range.");
}
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Movement Boost",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Movement Boost");
nullRing2.lock()->_EnchantItem(ItemEnchant{"Movement Boost"});
Test::InRange(player->GetMoveSpdMult(),{106.0_Pct,110.0_Pct},L"Move Speed not in expected range with two rings equipped.");
}
}
@ -159,12 +159,12 @@ namespace EnchantTests
Assert::AreEqual(0.0_Pct,player->GetCooldownReductionPct(),L"Player starts with 0% CDR.");
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Ability Haste")};
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Ability Haste");
nullRing.lock()->_EnchantItem(ItemEnchant{"Ability Haste"});
Test::InRange(player->GetCooldownReductionPct(),{3.0_Pct,5.0_Pct},L"CDR not in expected range.");
}
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Ability Haste",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Ability Haste");
nullRing2.lock()->_EnchantItem(ItemEnchant{"Ability Haste"});
Test::InRange(player->GetCooldownReductionPct(),{6.0_Pct,10.0_Pct},L"CDR not in expected range with two rings.");
}
}
@ -172,12 +172,12 @@ namespace EnchantTests
Assert::AreEqual(0.0_Pct,player->GetCritRatePct(),L"Player starts with 0% Crit Rate.");
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Crit Rate")};
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Crit Rate");
nullRing.lock()->_EnchantItem(ItemEnchant{"Crit Rate"});
Test::InRange(player->GetCritRatePct(),{3.0_Pct,5.0_Pct},L"Crit Rate not in expected range.");
}
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Crit Rate",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Crit Rate");
nullRing2.lock()->_EnchantItem(ItemEnchant{"Crit Rate"});
Test::InRange(player->GetCritRatePct(),{6.0_Pct,10.0_Pct},L"Crit Rate not in expected range with two rings.");
}
}
@ -185,12 +185,12 @@ namespace EnchantTests
Assert::AreEqual(50.0_Pct,player->GetCritDmgPct(),L"Player starts with 50% Crit Damage.");
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Crit Damage")};
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Crit Damage");
nullRing.lock()->_EnchantItem(ItemEnchant{"Crit Damage"});
Test::InRange(player->GetCritDmgPct(),{57.0_Pct,60.0_Pct},L"Crit Damage not in expected range.");
}
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Crit Damage",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Crit Damage");
nullRing2.lock()->_EnchantItem(ItemEnchant{"Crit Damage"});
Test::InRange(player->GetCritDmgPct(),{64.0_Pct,70.0_Pct},L"Crit Damage not in expected range with two rings.");
}
}
@ -198,12 +198,12 @@ namespace EnchantTests
Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Player starts with 0% Damage Reduction.");
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Stoneskin")};
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Stoneskin");
nullRing.lock()->_EnchantItem(ItemEnchant{"Stoneskin"});
Test::InRange(player->GetDamageReductionFromBuffs(),{3.0_Pct,5.0_Pct},L"Damage Reduction not in expected range.");
}
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Stoneskin",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Stoneskin");
nullRing2.lock()->_EnchantItem(ItemEnchant{"Stoneskin"});
Test::InRange(player->GetDamageReductionFromBuffs(),{6.0_Pct,10.0_Pct},L"Damage Reduction not in expected range with two rings.");
}
}
@ -211,12 +211,12 @@ namespace EnchantTests
Assert::AreEqual(100,player->GetMaxMana(),L"Player starts with 100 mana.");
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Mana Pool")};
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Mana Pool");
nullRing.lock()->_EnchantItem(ItemEnchant{"Mana Pool"});
Test::InRange(player->GetMaxMana(),{107,112},L"Mana Pool not in expected range.");
}
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Mana Pool",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Mana Pool");
nullRing2.lock()->_EnchantItem(ItemEnchant{"Mana Pool"});
Test::InRange(player->GetMaxMana(),{114,124},L"Mana Pool not in expected range with two rings.");
}
}
@ -227,7 +227,7 @@ namespace EnchantTests
Assert::AreEqual(0.0_Pct,player->GetHP6RecoveryPct(),L"Player starts with 0% HP/6 recovery.");
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Magical Protection")};
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Magical Protection");
nullRing.lock()->_EnchantItem(ItemEnchant{"Magical Protection"});
Test::InRange(player->GetMaxHealth(),{102,103},L"Max Health not in expected range.");
Test::InRange(player->GetDamageReductionFromBuffs(),{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.");
@ -235,7 +235,7 @@ namespace EnchantTests
}
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Magical Protection",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Magical Protection");
nullRing2.lock()->_EnchantItem(ItemEnchant{"Magical Protection"});
Test::InRange(player->GetMaxHealth(),{102,103},L"Max Health not in expected range with two rings.");
Test::InRange(player->GetDamageReductionFromBuffs(),{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.");
@ -250,7 +250,7 @@ namespace EnchantTests
Assert::AreEqual(50.0_Pct,player->GetCritDmgPct(),L"Player starts with 50% crit rate.");
std::weak_ptr<Item>nullRing{Game::GiveAndEquipEnchantedRing("Aura of the Beast")};
for(int i:std::ranges::iota_view(0,1000)){
nullRing.lock()->_EnchantItem("Aura of the Beast");
nullRing.lock()->_EnchantItem(ItemEnchant{"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.");
@ -258,7 +258,7 @@ namespace EnchantTests
}
std::weak_ptr<Item>nullRing2{Game::GiveAndEquipEnchantedRing("Aura of the Beast",EquipSlot::RING2)};
for(int i:std::ranges::iota_view(0,1000)){
nullRing2.lock()->_EnchantItem("Aura of the Beast");
nullRing2.lock()->_EnchantItem(ItemEnchant{"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.");

@ -250,24 +250,54 @@ namespace ItemTests
TEST_METHOD(EnchantTestCheck){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
Assert::IsFalse(slimeKingRing.lock()->HasEnchant());
bool obtainedDuplicate{false};
for(int i:std::ranges::iota_view(0,1000)){
std::optional<ItemEnchant>previousEnchant{slimeKingRing.lock()->GetEnchant()};
std::string previousEnchantName{};
if(slimeKingRing.lock()->HasEnchant())previousEnchantName=slimeKingRing.lock()->GetEnchant().value().Name();
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchantName));
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchant));
Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
Assert::AreNotEqual(previousEnchantName,slimeKingRing.lock()->GetEnchant().value().Name(),L"The previous enchant should never be the same as the new enchant attempt.");
if(previousEnchantName==slimeKingRing.lock()->GetEnchant().value().Name()){
bool improvementExists{false};
std::string statDowngrades{};
if(previousEnchant.value().HasAttributes()){
for(const auto&[attr,val]:previousEnchant.value()){
if(slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName())>val){
improvementExists=true;
break;
}else statDowngrades+=std::format("{} - Previous:{}, New:{}\n",attr.Name(),val,slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName()));
}
}else improvementExists=true;
Assert::IsTrue(improvementExists,util::wformat("Could not find a stat improvement for same name stat! {} THIS SHOULD NOT BE ALLOWED!",statDowngrades).c_str());
obtainedDuplicate=true;
}
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)){
std::optional<ItemEnchant> previousEnchant{slimeKingRing.lock()->GetEnchant()};
std::string previousEnchantName{};
if(slimeKingRing.lock()->HasEnchant())previousEnchantName=slimeKingRing.lock()->GetEnchant().value().Name();
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchantName));
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchant));
Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
Assert::AreNotEqual(previousEnchantName,slimeKingRing.lock()->GetEnchant().value().Name(),L"The previous enchant should never be the same as the new enchant attempt.");
if(previousEnchantName==slimeKingRing.lock()->GetEnchant().value().Name()){
bool improvementExists{false};
std::string statDowngrades{};
if(previousEnchant.value().HasAttributes()){
for(const auto&[attr,val]:previousEnchant.value()){
if(slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName())>val){
improvementExists=true;
break;
}else statDowngrades+=std::format("{} - Previous:{}, New:{}\n",attr.Name(),val,slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName()));
}
}else improvementExists=true;
Assert::IsTrue(improvementExists,util::wformat("Could not find a stat improvement for same name stat! {} THIS SHOULD NOT BE ALLOWED!",statDowngrades).c_str());
obtainedDuplicate=true;
}
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.
}
Assert::IsTrue(obtainedDuplicate,L"During this test a duplicate enchant was never obtained! THIS SHOULD BE ALLOWED!");
}
TEST_METHOD(AccessoryAntiCompatibilityCheck){
std::weak_ptr<Item>extraRing{Inventory::AddItem("Ring of the Slime King"s)};
@ -285,7 +315,7 @@ namespace ItemTests
for(int i:std::ranges::iota_view(0,1000)){
Inventory::AddItem(extraRing.lock()->FragmentName(),"Fragment Enchant Cost"_i[0]);
player->AddMoney("Fragment Enchant Cost"_i[1]);
ItemEnchantInfo resultEnchant{extraRing.lock()->ApplyRandomEnchant()};
const ItemEnchant&resultEnchant{extraRing.lock()->ApplyRandomEnchant()};
if(resultEnchant.GetClass().has_value())Assert::AreEqual(int(resultEnchant.GetClass().value()),int(player->GetClass()),L"Player's class matches the class of the enchant.");
enchantCounts[resultEnchant.Category()]++;
Assert::AreEqual(true,extraRing.lock()->GetEnchant().has_value(),L"Ring is expected to be enchanted.");

@ -629,17 +629,17 @@ namespace PlayerTests
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");
slimeKingRing.lock()->_EnchantItem("Emergency Recovery"sv);
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");
slimeKingRing.lock()->_EnchantItem("Reaper of Souls"sv);
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");
slimeKingRing.lock()->_EnchantItem("Attack Boost"sv);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
@ -658,18 +658,18 @@ namespace PlayerTests
Inventory::EquipItem(leatherShoes,EquipSlot::SHOES);
Inventory::EquipItem(woodenSword,EquipSlot::WEAPON);
leatherHelmet.lock()->_EnchantItem("Wizard's Soul");
leatherHelmet.lock()->_EnchantItem("Wizard's Soul"sv);
Assert::IsTrue(player->HasEnchant("Wizard's Soul"));
leatherArmor.lock()->_EnchantItem("Ability Haste");
leatherArmor.lock()->_EnchantItem("Ability Haste"sv);
Assert::IsTrue(player->HasEnchant("Ability Haste"));
leatherPants.lock()->_EnchantItem("Improved Ground Slam");
leatherPants.lock()->_EnchantItem("Improved Ground Slam"sv);
Assert::IsTrue(player->HasEnchant("Improved Ground Slam"));
leatherGloves.lock()->_EnchantItem("Battle Shout");
leatherGloves.lock()->_EnchantItem("Battle Shout"sv);
Assert::IsTrue(player->HasEnchant("Battle Shout"));
leatherShoes.lock()->_EnchantItem("Attack Boost");
leatherShoes.lock()->_EnchantItem("Attack Boost"sv);
Inventory::UnequipItem(EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
woodenSword.lock()->_EnchantItem("Mana Pool");
woodenSword.lock()->_EnchantItem("Mana Pool"sv);
Assert::IsTrue(player->HasEnchant("Mana Pool"));
Inventory::UnequipItem(EquipSlot::HELMET);
@ -744,7 +744,7 @@ namespace PlayerTests
player->GetAbility3().cooldown=player->GetAbility3().GetCooldownTime();
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->_EnchantItem("Multi-Multishot");
nullRing.lock()->_EnchantItem("Multi-Multishot"sv);
testKey->bHeld=true; //Force the key to be held down for testing purposes.
Assert::AreEqual(player->GetAbility3().GetCooldownTime(),oldCooldownTime-oldCooldownTime*"Multi-Multishot"_ENC["COOLDOWN REDUCTION PCT"]/100.f,L"Old cooldown time with multishot cooldown reduction pct matches.");
testGame->SetElapsedTime(0.f);

@ -436,7 +436,7 @@ bool AiL::OnConsoleCommand(const std::string& sCommand){
if(args[0]=="/accessory"){
if(args.size()<2)ConsoleOut()<<"Usage: /accessory <Accessory Name> [Enchant Name]"<<std::endl;
std::weak_ptr<Item>accessory{Inventory::AddItem(args[1])};
if(args.size()>=3)accessory.lock()->_EnchantItem(args[2]);
if(args.size()>=3)accessory.lock()->_EnchantItem(ItemEnchant{args[2]});
ConsoleOut()<<"Added "<<args[1]<<" to player's inventory."<<std::endl;
}else{
ConsoleOut()<<"Invalid command! Use /help to see available commands."<<std::endl;

@ -69,9 +69,10 @@ void Menu::InitializeArtificerEnchantConfirmWindow(){
const float takeOldTextWidth{float(game->GetTextSize("Take Old").x)*2.f};
auto takeOldButton{artificerEnchantConfirmWindow->ADD("Take Old Button",MenuComponent)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/4.f-takeOldTextWidth/2.f,138.f},{takeOldTextWidth-8.f,20.f}},"Take Old",[](MenuFuncData data){
onClick:
const std::string&oldEnchantName{Menu::menus[ARTIFICER_ENCHANT]->S(A::ENCHANT_TYPE)};
std::any&oldEnchant{Menu::menus[ARTIFICER_ENCHANT]->ANY(A::ENCHANT)};
std::weak_ptr<Item>newItem{std::get<std::weak_ptr<Item>>(Component<MenuItemLabel>(data.menu.GetType(),"New Item Description")->GetItem())}; //NOTE: We're making an assumption here that the new item description holds a weak pointer. This should be true because the only way to get here was to set it up through clicking the Enchant button in Artificer Enchant Window.
newItem.lock()->_EnchantItem(oldEnchantName);
if(oldEnchant.has_value())newItem.lock()->_EnchantItem(std::any_cast<ItemEnchant&>(oldEnchant));
else newItem.lock()->RemoveEnchant();
Component<MenuComponent>(ARTIFICER_ENCHANT,"Fragment Enchant Button")->SetGrayedOut(!newItem.lock()->CanBeEnchanted());
const std::string_view fragmentName{newItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Enchant Cost"_i[0]?WHITE:RED};

@ -139,8 +139,8 @@ void Menu::InitializeArtificerEnchantWindow(){
SoundEffect::PlaySFX("Enchant Item",SoundEffect::CENTERED);
std::weak_ptr<Item>item{Component<MenuItemItemButton>(data.menu.type,"Item Icon")->GetItem()};
std::optional<ItemEnchant>previousEnchant{item.lock()->GetEnchant()};
data.menu.S(A::ENCHANT_TYPE)="";
if(previousEnchant.has_value())data.menu.S(A::ENCHANT_TYPE)=previousEnchant.value().Name();
data.menu.ANY(A::ENCHANT)={};
if(previousEnchant.has_value())data.menu.ANY(A::ENCHANT)=previousEnchant.value();
Component<MenuIconButton>(ARTIFICER_ENCHANT_CONFIRM,"Old Item Icon")->SetIcon(item.lock()->Icon().Decal());
Component<MenuItemLabel>(ARTIFICER_ENCHANT_CONFIRM,"Old Item Description")->SetItem(*item.lock());

@ -40,50 +40,58 @@ All rights reserved.
#include "DEFINES.h"
#include <variant>
using namespace std::literals;
class IAttributable{
public:
inline virtual ~IAttributable(){};
std::map<Attribute,std::variant<VARIANTS>>attributes;
std::map<Attribute,std::any>attributes;
inline float&GetFloat(Attribute a){
if(attributes.count(a)==0){
attributes[a]=0.f;
}
return std::get<float>(attributes[a]);
return std::any_cast<float&>(attributes[a]);
};
inline int&GetInt(Attribute a){
if(attributes.count(a)==0){
attributes[a]=0;
}
return std::get<int>(attributes[a]);
return std::any_cast<int&>(attributes[a]);
};
inline std::string&GetString(Attribute a){
if(attributes.count(a)==0){
attributes[a]="";
attributes[a]=""s;
}
return std::get<std::string>(attributes[a]);
return std::any_cast<std::string&>(attributes[a]);
};
inline bool&GetBool(Attribute a){
if(attributes.count(a)==0){
attributes[a]=false;
}
return std::get<bool>(attributes[a]);
return std::any_cast<bool&>(attributes[a]);
};
inline vf2d&GetVf2d(Attribute a){
if(attributes.count(a)==0){
attributes[a]=vf2d{};
}
return std::get<vf2d>(attributes[a]);
return std::any_cast<vf2d&>(attributes[a]);
};
inline std::vector<std::any>&GetVec(Attribute a){
if(attributes.count(a)==0){
attributes[a]=std::vector<std::any>{};
}
return std::get<std::vector<std::any>>(attributes[a]);
return std::any_cast<std::vector<std::any>&>(attributes[a]);
};
inline std::any&GetAny(Attribute a){
if(attributes.count(a)==0){
attributes[a]=std::any{};
}
return attributes[a];
};
inline size_t&GetSizeT(Attribute a){
if(attributes.count(a)==0){
attributes[a]=size_t(0);
}
return std::get<size_t>(attributes[a]);
return std::any_cast<size_t&>(attributes[a]);
};
};

@ -113,6 +113,9 @@ public:
inline auto end()const{
return attributes.end();
}
inline auto size()const{
return attributes.size();
}
inline void clear(){
attributes.clear();
}

@ -1466,8 +1466,8 @@ RefineResult Item::Refine(){
return RefineResult{chosenAttr,newAmt-oldAmt};
}
void Item::_EnchantItem(const std::string_view enchantName){
enchant=ItemEnchant{enchantName};
void Item::_EnchantItem(const ItemEnchant&newEnchant){
enchant=newEnchant;
game->GetPlayer()->RecalculateEquipStats();
}
@ -1528,13 +1528,15 @@ const std::optional<std::string>&Item::FragmentIcon()const{
return ITEM_DATA.at(FragmentName()).FragmentIcon();
}
const ItemEnchantInfo Item::ApplyRandomEnchant(){
const ItemEnchant&Item::ApplyRandomEnchant(){
if(!CanBeEnchanted())ERR("WARNING! Trying to enchant an item that cannot be enchanted! Make sure you are checking with CanBeEnchanted() before calling this function!");
std::string currentEnchantName{""};
if(HasEnchant())currentEnchantName=GetEnchant().value().Name();
EnchantName randomEnchantName{ItemEnchant::RollRandomEnchant(currentEnchantName)};
_EnchantItem(randomEnchantName);
ItemEnchant randomEnchant{ItemEnchant::RollRandomEnchant(GetEnchant())};
_EnchantItem(randomEnchant);
Inventory::RemoveItem(FragmentName(),"Fragment Enchant Cost"_i[0]);
game->GetPlayer()->RemoveMoney("Fragment Enchant Cost"_i[1]);
return ItemEnchantInfo::GetEnchant(randomEnchantName);
return GetEnchant().value();
}
void Item::RemoveEnchant(){
enchant.reset();
}

@ -257,11 +257,12 @@ public:
const bool CanBeEnchanted()const;
void Lock();
void Unlock();
void _EnchantItem(const std::string_view enchantName);
void RemoveEnchant();
void _EnchantItem(const ItemEnchant&newEnchant);
//Applies a random valid enchant to an item and consumes the materials in the process.
//Assumes an item can be enchanted. CHECK WITH CanBeEnchanted() First!!!
//Enchants the item based on its class parameters defined in the ItemEnchants.txt configuration file. Also takes away the costs required to refine the item (Located in Accessories.txt).
const ItemEnchantInfo ApplyRandomEnchant();
const ItemEnchant&ApplyRandomEnchant();
const 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;};

@ -246,7 +246,7 @@ const std::vector<ItemEnchantInfo>ItemEnchant::GetAvailableEnchants(){
return filteredEnchants;
}
const std::string ItemEnchant::RollRandomEnchant(const std::string_view previousEnchantName){
const ItemEnchant ItemEnchant::RollRandomEnchant(const std::optional<ItemEnchant>previousEnchant){
const std::vector<ItemEnchantInfo>filteredEnchants{GetAvailableEnchants()};
int randomRoll{int(util::random_range(0,100))};
@ -256,14 +256,28 @@ const std::string ItemEnchant::RollRandomEnchant(const std::string_view previous
std::vector<ItemEnchantInfo>remainingAvailableEnchants;
if(randomRoll>=generalEnchantRange.first&&randomRoll<generalEnchantRange.second){
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[&previousEnchantName](const ItemEnchantInfo&enchant){return enchant.Name()!=previousEnchantName&&enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::GENERAL;});
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[](const ItemEnchantInfo&enchant){return enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::GENERAL;});
}else if(randomRoll>=classEnchantRange.first&&randomRoll<classEnchantRange.second){
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[&previousEnchantName](const ItemEnchantInfo&enchant){return enchant.Name()!=previousEnchantName&&enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::CLASS;});
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[](const ItemEnchantInfo&enchant){return enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::CLASS;});
}else if(randomRoll>=uniqueEnchantRange.first&&randomRoll<uniqueEnchantRange.second){
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[&previousEnchantName](const ItemEnchantInfo&enchant){return enchant.Name()!=previousEnchantName&&enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::UNIQUE;});
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[](const ItemEnchantInfo&enchant){return enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::UNIQUE;});
}else ERR(std::format("WARNING! Somehow did not pick a value in any of the given ranges. Rolled value was: {}. Ranges were {}-{}, {}-{}, {}-{}",randomRoll,generalEnchantRange.first,generalEnchantRange.second,classEnchantRange.first,classEnchantRange.second,uniqueEnchantRange.first,uniqueEnchantRange.second));
return remainingAvailableEnchants[util::random()%remainingAvailableEnchants.size()].Name();
const auto AtLeastOneAttributeIncreased=[](const ItemEnchant&oldEnchant,const ItemEnchant&newEnchant){
if(oldEnchant.Name()!=newEnchant.Name())ERR(std::format("WARNING! Not intended to be used with enchants of varying names!! Old:{} New:{}",oldEnchant.Name(),newEnchant.Name()));
for(const auto&[attr,val]:oldEnchant){
if(newEnchant.GetAttribute(attr.ActualName())>val)return true;
}
return false;
};
for(;;){
ItemEnchantInfo&randomEnchant{remainingAvailableEnchants[util::random()%remainingAvailableEnchants.size()]};
ItemEnchant newEnchant{randomEnchant.Name()};
if(!previousEnchant||
previousEnchant.value().Name()!=newEnchant.Name()||
AtLeastOneAttributeIncreased(previousEnchant.value(),newEnchant))return newEnchant;
}
}
void ItemEnchant::UpdateDescription(){
@ -328,4 +342,12 @@ const std::optional<std::reference_wrapper<Ability>>ItemEnchant::Ability()const{
const Pixel&ItemEnchantInfo::DisplayCol()const{
return ItemEnchantInfo::enchantTextDisplayCol.at(Category());
}
const bool ItemEnchant::HasAttributes()const{
return stats.size()>0;
}
const std::optional<Class>&ItemEnchant::GetClass()const{
return ItemEnchantInfo::GetEnchant(Name()).GetClass();
}

@ -111,13 +111,15 @@ public:
const std::optional<ItemEnchantInfo::AbilitySlot>&AbilitySlot()const;
const static std::vector<ItemEnchantInfo>GetAvailableEnchants();
//Rolls a class-appropriate random enchant.
const static std::string RollRandomEnchant(const std::string_view previousEnchantName);
const static ItemEnchant RollRandomEnchant(const std::optional<ItemEnchant>previousEnchant={});
void SetAttribute(const std::string_view attr,const float val);
const float&GetAttribute(const std::string_view attr)const;
std::map<ItemAttribute,float>::const_iterator begin()const;
std::map<ItemAttribute,float>::const_iterator end()const;
const bool HasAttributes()const;
const Pixel&DisplayCol()const;
const std::optional<std::reference_wrapper<::Ability>>Ability()const;
const std::optional<Class>&GetClass()const;
private:
void UpdateDescription();
const ItemEnchantInfo&GetEnchantInfo()const;

@ -44,6 +44,7 @@ All rights reserved.
#define B(attr) GetBool(attr)
#define V(attr) GetVf2d(attr)
#define VEC(attr) GetVec(attr)
#define ANY(attr) GetAny(attr)
enum class Attribute{
IFRAME_TIME_UPON_HIT, //When this is set, the monster gains iframes if they take damage based on the value this is set to.
@ -145,5 +146,5 @@ enum class Attribute{
SHOCKWAVE_COLOR,
FIRE_VELOCITY,
EXTENDED_LINE,
ENCHANT_TYPE,
ENCHANT,
};

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 3
#define VERSION_PATCH 0
#define VERSION_BUILD 11576
#define VERSION_BUILD 11592
#define stringify(a) stringify_(a)
#define stringify_(a) #a

Loading…
Cancel
Save