Stage and Monster Loot tables only populate in DUNGEON and BOSS map types. Moved the inventory generator UI updating code for loading and saving the game to the very end of the load/save process. Reducing the number of calls dramatically for improved loading speeds. Release Build 12811. 235/235 Tests passing.
Some checks failed
Emscripten Build / Build_and_Deploy_Web_Build (push) Failing after 6m14s

This commit is contained in:
sigonasr2 2026-02-27 15:48:42 -06:00
parent 829388d9d3
commit 974b4ad226
24 changed files with 237 additions and 101 deletions

View File

@ -83,7 +83,7 @@ namespace BuffTests
#pragma region Setup a fake test map
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
Game::_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
#pragma endregion

View File

@ -84,7 +84,7 @@ namespace FileTests
#pragma region Setup a fake test map
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
Game::_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
#pragma endregion

View File

@ -90,10 +90,22 @@ namespace Game{
pl=game->GetPlayer(); //The player pointer has been reassigned...
}
inline void LoadLevel(const std::string&mapKeyname,AiL*const game){
inline void LoadLevelWithTMX(const std::string&mapKeyname,AiL*const game){
game->InitializeLevel("map_path"_S+DATA["Levels"][mapKeyname]["Map File"].GetString(),mapKeyname);
game->SetupLevel(game->MAP_DATA[mapKeyname]);
game->_SetCurrentLevel(mapKeyname);
_SetCurrentLevel(mapKeyname);
}
inline void LoadFakeLevel(const std::string&mapKeyname,AiL*const game){
game->MAP_DATA.at(mapKeyname).name=mapKeyname;
game->SetupLevel(game->MAP_DATA[mapKeyname]);
_SetCurrentLevel(mapKeyname);
}
//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.
inline void _SetCurrentLevel(const MapName map){
game->ResetLevelStates();
game->currentLevel=map;
}
}

View File

@ -43,6 +43,8 @@ All rights reserved.
#include "ItemDrop.h"
#include <ranges>
#include "GameHelper.h"
#include"RowInventoryScrollableWindowComponent.h"
#include"SaveFile.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -71,6 +73,9 @@ namespace ItemTests
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
Monster::InitializeStrategies();
MonsterData::InitializeMonsterData();
sig::Animation::InitializeAnimations();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
@ -84,6 +89,8 @@ namespace ItemTests
#pragma region Setup a fake test map and test monster
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA["HUB_TYPE_TEST_MAP"];
game->MAP_DATA["WORLD_TYPE_TEST_MAP"];
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
@ -97,6 +104,9 @@ namespace ItemTests
Menu::themes.SetInitialized();
GFX.SetInitialized();
game->MAP_DATA.SetInitialized();
MapTag dungeonMapData{};
game->MAP_DATA["HUB_TYPE_TEST_MAP"].mapType=Map::MapType::HUB;
game->MAP_DATA["WORLD_TYPE_TEST_MAP"].mapType=Map::MapType::WORLD_MAP;
}
TEST_METHOD_CLEANUP(ItemCleanupTests){
testGame->EndGame();
@ -107,6 +117,46 @@ namespace ItemTests
Inventory::AddItem("Health Potion"s,3);
Assert::AreEqual(3U,Inventory::GetItemCount("Health Potion"s),L"Player has 3 Health Potions.");
}
TEST_METHOD(ItemAddAndRemoveAffectsStageLootTest){
Assert::AreEqual(size_t(0),Inventory::get("Stage Loot").size(),L"Stage Loot has 0 Items in it when the game starts.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in it when the game starts.");
Game::LoadLevelWithTMX("CAMPAIGN_1_1",testGame.get());
Assert::AreEqual(size_t(0),Inventory::get("Stage Loot").size(),L"Stage Loot has 0 Items in it after loading a new stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in it after loading a new stage.");
Inventory::AddItem("Health Potion"s,3);
Assert::AreEqual(size_t(1),Inventory::get("Stage Loot").size(),L"Stage Loot has 1 Item in it after collecting an item in a dungeon stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in it after collecting a non-monster drop item in a dungeon stage.");
Inventory::AddItem("Health Potion"s,3,true);
Assert::AreEqual(size_t(1),Inventory::get("Stage Loot").size(),L"Stage Loot has 1 Item in it after collecting an item in a dungeon stage.");
Assert::AreEqual(size_t(1),Inventory::get("Monster Loot").size(),L"Monster Loot has 1 Item in it after collecting a monster drop item in a dungeon stage.");
Game::LoadLevelWithTMX("BOSS_1",testGame.get());
Assert::AreEqual(size_t(0),Inventory::get("Stage Loot").size(),L"Stage Loot has 0 Items in it after loading a new stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in it after loading a new stage.");
Inventory::AddItem("Health Potion"s,3);
Assert::AreEqual(size_t(1),Inventory::get("Stage Loot").size(),L"Stage Loot has 1 Item in it after collecting an item in a boss stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in it after collecting a non-monster item in a boss stage.");
Inventory::AddItem("Health Potion"s,3,true);
Assert::AreEqual(size_t(1),Inventory::get("Stage Loot").size(),L"Stage Loot has 1 Item in it after collecting an item in a boss stage.");
Assert::AreEqual(size_t(1),Inventory::get("Monster Loot").size(),L"Monster Loot has 1 Item in it after collecting a monster drop item in a boss stage.");
Game::LoadFakeLevel("HUB_TYPE_TEST_MAP",testGame.get());
Assert::AreEqual(size_t(0),Inventory::get("Stage Loot").size(),L"Stage Loot has 0 Items in it after loading a new stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in it after loading a new stage.");
Inventory::AddItem("Health Potion"s,3);
Assert::AreEqual(size_t(0),Inventory::get("Stage Loot").size(),L"Stage Loot has 0 Items in it due to not being a dungeon or boss stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in it due to not being a dungeon or boss stage.");
Inventory::AddItem("Health Potion"s,3,true);
Assert::AreEqual(size_t(0),Inventory::get("Stage Loot").size(),L"Stage Loot has 0 Items in it when picking up monster drops due to not being a dungeon or boss stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in when picking up monster drops it due to not being a dungeon or boss stage.");
Game::LoadFakeLevel("WORLD_TYPE_TEST_MAP",testGame.get());
Assert::AreEqual(size_t(0),Inventory::get("Stage Loot").size(),L"Stage Loot has 0 Items in it after loading a new stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in it after loading a new stage.");
Inventory::AddItem("Health Potion"s,3);
Assert::AreEqual(size_t(0),Inventory::get("Stage Loot").size(),L"Stage Loot has 0 Items in it due to not being a dungeon or boss stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in it due to not being a dungeon or boss stage.");
Inventory::AddItem("Health Potion"s,3,true);
Assert::AreEqual(size_t(0),Inventory::get("Stage Loot").size(),L"Stage Loot has 0 Items in it when picking up monster drops due to not being a dungeon or boss stage.");
Assert::AreEqual(size_t(0),Inventory::get("Monster Loot").size(),L"Monster Loot has 0 Items in when picking up monster drops it due to not being a dungeon or boss stage.");
}
TEST_METHOD(ItemRemoveTest){
Inventory::AddItem("Health Potion"s,3);
for(std::weak_ptr<Item>&item:Inventory::GetItem("Health Potion"s))Inventory::RemoveItem(item,3);
@ -353,5 +403,28 @@ namespace ItemTests
Assert::AreEqual(true,extraRing.lock()->CanBeEnchanted(),L"Ring can be enchanted again with the right amount of fragments.");
Assert::AreEqual(uint32_t(2000-"Fragment Enchant Cost"_i[1]),player->GetMoney(),util::wformat("Lost {} money due to enchanting ring.","Fragment Enchant Cost"_i[1]).c_str());
}
TEST_METHOD(UIUpdatesWhenItemsAreReceivedTest){
Inventory::AddItem("Health Potion"s,3);
for(const auto&category:Item::GetItemCategories()|std::views::filter([](const std::string&category){return DATA["ItemCategory"][category].GetString()!="!HIDE";})){
const size_t itemCount{Component<RowInventoryScrollableWindowComponent>(MenuType::INVENTORY,std::format("Inventory Display - {}",category))->GetMainComponentCount()};
Assert::AreEqual(Inventory::get(category).size(),itemCount,util::wformat("UI component count in inventory window does not match player inventory item count in category {}",category).c_str());
}
}
TEST_METHOD(UIUpdatesWhenGameFileWithItemsIsLoadedTest){
SaveFile::SetSaveFileID(-1);
SaveFile::LoadGame();
for(const auto&category:Item::GetItemCategories()|std::views::filter([](const std::string&category){return DATA["ItemCategory"][category].GetString()!="!HIDE";})){
const size_t itemCount{Component<RowInventoryScrollableWindowComponent>(MenuType::INVENTORY,std::format("Inventory Display - {}",category))->GetMainComponentCount()};
Assert::AreEqual(Inventory::get(category).size(),itemCount,util::wformat("UI component count in inventory window does not match player inventory item count in category {}",category).c_str());
}
}
TEST_METHOD(UIDoesNotUpdateWhenSavingFileFlagIsEnabledTest){
game->savingFile=true;
Inventory::AddItem("Health Potion"s,3);
for(const auto&category:Item::GetItemCategories()|std::views::filter([](const std::string&category){return DATA["ItemCategory"][category].GetString()!="!HIDE";})){
const size_t itemCount{Component<RowInventoryScrollableWindowComponent>(MenuType::INVENTORY,std::format("Inventory Display - {}",category))->GetMainComponentCount()};
Assert::AreEqual(size_t(0),itemCount,util::wformat("UI component count in inventory window is still empty in category {} during file saving",category).c_str());
}
}
};
}

View File

@ -90,7 +90,7 @@ namespace MonsterTests
#pragma region Setup a fake test map
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
Game::_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
game->MAP_DATA.SetInitialized();
#pragma endregion
@ -560,7 +560,7 @@ namespace MonsterTests
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
}
TEST_METHOD(MonsterRunRightTest){
Game::LoadLevel("TEST_MAP",testGame.get());
Game::LoadLevelWithTMX("TEST_MAP",testGame.get());
testGame->GetPlayer()->ForceSetPos({100,100});
Monster&boar{game->SpawnMonster({24,24},MONSTER_DATA.at("Boar"))};
Game::Update(0.f); // Make sure monster spawns first.
@ -570,7 +570,7 @@ namespace MonsterTests
Assert::IsTrue(originalBoarPos+vf2d{50.f*boar.GetMoveSpdMult(),0.f}==boar.GetPos(),util::wformat("The boar should have moved {} units in half a second. Expected {}=={}",50.f*boar.GetMoveSpdMult(),(originalBoarPos+vf2d{50.f*boar.GetMoveSpdMult(),0.f}).str(),boar.GetPos().str()).c_str());
}
TEST_METHOD(MonsterHasteMoveTest){
Game::LoadLevel("TEST_MAP",testGame.get());
Game::LoadLevelWithTMX("TEST_MAP",testGame.get());
Monster&parrot{game->SpawnMonster({24,24},MONSTER_DATA.at("Parrot"))};
Game::Update(0.f); // Make sure monster spawns first.
parrot.strategy="[TEST]Run Right";

View File

@ -802,7 +802,7 @@ namespace PlayerTests
Assert::AreEqual(player->GetMaxHealth()-10,player->GetHealth(),L"Player now takes damage with 0 shield.");
}
TEST_METHOD(PlayerHasteMoveCheck){
Game::LoadLevel("TEST_MAP",testGame.get());
Game::LoadLevelWithTMX("TEST_MAP",testGame.get());
player=testGame->GetPlayer();
player->ForceSetPos({12,12});
testKeyboardInput.AddKeybind(Input{InputType::KEY,D});
@ -829,7 +829,7 @@ namespace PlayerTests
}
}
TEST_METHOD(PlayerHasteCooldownCheck){
Game::LoadLevel("TEST_MAP",testGame.get());
Game::LoadLevelWithTMX("TEST_MAP",testGame.get());
player=testGame->GetPlayer();
player->ForceSetPos({12,12});
testKeyboardInput.AddKeybind(Input{InputType::KEY,D});

View File

@ -2281,10 +2281,16 @@ void AiL::InitializeLevel(std::string mapFile,MapName map){
size_t slashMarker = mapFile.find_last_of('/');
std::string baseDir=mapFile.substr(0,slashMarker+1);
if(TestingModeEnabled())MAP_DATA.Unlock();
MAP_DATA[map]=level.GetData();
if(TestingModeEnabled()){
if(!MAP_DATA.contains(map)){
MAP_DATA.Unlock();
MAP_DATA[map]=level.GetData();
MAP_DATA.SetInitialized();
}
}else{
MAP_DATA[map]=level.GetData();
}
MAP_DATA.at(map).name=map;
if(TestingModeEnabled())MAP_DATA.SetInitialized();
}
void AiL::LoadLevel(MapName map,MusicChange changeMusic){
@ -3827,9 +3833,20 @@ const std::weak_ptr<Item>AiL::GetLoadoutItem(int slot){
}
void AiL::RestockLoadoutItems(){
for(int i=0;i<GetLoadoutSize();i++){
if(!ISBLANK(GetLoadoutItem(i))){
SetLoadoutItem(i,GetLoadoutItem(i).lock()->ActualName());
for(int slot=0;slot<GetLoadoutSize();slot++){
if(!ISBLANK(GetLoadoutItem(slot))){
SetLoadoutItem(slot,GetLoadoutItem(slot).lock()->ActualName());
//Set the loadout slot selection for this loadout item.
auto&itemsList=Component<InventoryScrollableWindowComponent>(INVENTORY_CONSUMABLES,"inventory")->GetComponents();
auto item=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&component){
if(DYNAMIC_POINTER_CAST<MenuItemButton>(component)->GetItem().lock()->ActualName()==loadout[slot]->ActualName()){
return true;
}
return false;
});
if(item!=itemsList.end())DYNAMIC_POINTER_CAST<MenuItemButton>(*item)->selected=slot;
else ERR(std::format("WARNING! Could not find item {} in inventory while selecting a loadout item from the consumables menu! THIS SHOULD NOT BE HAPPENING!",loadout[slot]->ActualName()));
}
}
}
@ -3895,18 +3912,6 @@ void AiL::SetLoadoutItem(int slot,std::string itemName){
}break;
}
//Set the loadout slot selection for this loadout item.
auto&itemsList=Component<InventoryScrollableWindowComponent>(INVENTORY_CONSUMABLES,"inventory")->GetComponents();
auto item=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&component){
if(DYNAMIC_POINTER_CAST<MenuItemButton>(component)->GetItem().lock()->ActualName()==loadout[slot]->ActualName()){
return true;
}
return false;
});
if(item!=itemsList.end()){
DYNAMIC_POINTER_CAST<MenuItemButton>(*item)->selected=slot;
}else ERR(std::format("WARNING! Could not find item {} in inventory while selecting a loadout item from the consumables menu! THIS SHOULD NOT BE HAPPENING!",loadout[slot]->ActualName()));
}else{
ERR("Trying to set item "+itemName+" in Loadout slot "+std::to_string(slot)+" when said item does not exist in our inventory!");
}
@ -4112,6 +4117,7 @@ void AiL::InitializePlayerLevelCap(){
}
void AiL::ResetGame(bool changeToMainMenu){
resettingGame=true;
if(changeToMainMenu){
GameState::ChangeState(States::MAIN_MENU,0.5f);
}
@ -4132,15 +4138,18 @@ void AiL::ResetGame(bool changeToMainMenu){
game->ClearLoadoutItem(i);
}
Unlock::Initialize();
State_OverworldMap::SetStageMarker("Player.Starting Location"_S);
State_OverworldMap::UpdateCurrentConnectionPoint(*State_OverworldMap::currentConnectionPoint);
State_OverworldMap::ResetConnectionPoints();
if(!TestingModeEnabled()){
State_OverworldMap::SetStageMarker("Player.Starting Location"_S);
State_OverworldMap::UpdateCurrentConnectionPoint(*State_OverworldMap::currentConnectionPoint);
State_OverworldMap::ResetConnectionPoints();
}
SetChapter(1);
SaveFile::SetSaveFileName("");
Tutorial::Initialize();
minimap.SetMinimapMode(MinimapMode::SMALL);
minimap.EraseChunkData();
ClearDecalInstances();
resettingGame=false;
}
void AiL::OnRequestCompleted(const std::string_view receivedData)const{
@ -4546,10 +4555,6 @@ void AiL::AddToSpecialMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCo
lockOnSpecialTargets.emplace_back(markData);
}
void AiL::_SetCurrentLevel(const MapName map){
currentLevel=map;
}
std::vector<Effect*>AiL::GetForegroundEffects()const{
std::vector<Effect*>outputVec;
std::for_each(foregroundEffects.begin(),foregroundEffects.end(),[&outputVec](const std::unique_ptr<Effect>&eff){outputVec.emplace_back(eff.get());});
@ -4594,6 +4599,8 @@ void AiL::ResetLevelStates(){
lockOnTargets.clear();
lockOnSpecialTargets.clear();
ItemDrop::drops.clear();
Inventory::Clear("Monster Loot");
Inventory::Clear("Stage Loot");
GameEvent::events.clear();
Audio::SetBGMPitch(1.f);
RepeatingSoundEffect::StopAllSounds();

View File

@ -90,9 +90,9 @@ enum class KnockbackCondition{
KNOCKBACK_UNHURT_TARGETS, //Knockback only targets that did not get hit.
};
namespace PlayerTests{
class PlayerTest;
}
namespace Game{
void _SetCurrentLevel(const MapName map);
};
class AiL : public olc::PixelGameEngine
{
@ -106,6 +106,7 @@ class AiL : public olc::PixelGameEngine
friend class Arc;
friend class LoadingScreen;
friend class TileGroupDataFile;
friend void Game::_SetCurrentLevel(const MapName map);
std::unique_ptr<Player>player;
SplashScreen splash;
public:
@ -332,7 +333,6 @@ public:
void AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData);
void AddToSpecialMarkedTargetList(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.
void InitializeCamera();
void AddNotification(const Notification&n);
@ -351,6 +351,8 @@ public:
};
void PrecacheNewLevels();
bool savingFile=false;
bool resettingGame=false;
private:
std::vector<std::unique_ptr<Effect>>foregroundEffects,backgroundEffects,foregroundEffectsToBeInserted,backgroundEffectsToBeInserted;
std::vector<TileRenderData*>tilesWithCollision,tilesWithoutCollision;
@ -405,7 +407,6 @@ private:
float loadingWaitTime=0.f;
bool displayHud=true;
float vignetteDisplayTime=0.f;
bool savingFile=false;
bool prevStageCompleted=false;
vf2d windSpd{};
bool MapCacheIsRunning{false};

View File

@ -59,6 +59,7 @@ All rights reserved.
INCLUDE_game
INCLUDE_GFX
#pragma region CharacterMenuWindow
namespace CharacterMenuWindow{
struct AttributeData{
std::string attrName;
@ -116,6 +117,7 @@ namespace CharacterMenuWindow{
return component;
}
};
#pragma endregion
void Menu::InitializeCharacterMenuWindow(){
static bool equipmentWindowOpened=false;

View File

@ -80,6 +80,7 @@ void Menu::InitializeHubPauseWindow(){
return true;
},ButtonAttr::FIT_TO_LABEL)END;
hubPauseWindow->ADD("Change Loadout Button",MenuComponent)(geom2d::rect<float>{{6.f,56.f},{84.f,24.f}},"Change\nLoadout",[](MenuFuncData data){
game->RestockLoadoutItems();
Menu::OpenMenu(ITEM_HUB_LOADOUT);
return true;
},ButtonAttr::FIT_TO_LABEL)END;

View File

@ -39,6 +39,8 @@ All rights reserved.
#include "RowInventoryScrollableWindowComponent.h"
#include "AccessoryRowItemDisplay.h"
INCLUDE_game
#define DEFINE(SubName) InventoryCreator InventoryCreator::SubName##_InventoryUpdate(&InventoryCreator::SubName##_InventorySlotsUpdate,&InventoryCreator::SubName##_AddButtonOnSlotUpdate);
DEFINE(Player);
@ -50,10 +52,12 @@ DEFINE(RowPlayerArmor);
#pragma region Player Inventory Updates
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::Player_InventorySlotsUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
size_t invSize=Inventory::get(cat).size();
component.RemoveAllComponents();
for(std::weak_ptr<Item> item:Inventory::get(cat)){
component.AddButtonOnSlotUpdate(cat);
if(!game->savingFile&&!game->resettingGame){
size_t invSize=Inventory::get(cat).size();
component.RemoveAllComponents();
for(std::weak_ptr<Item> item:Inventory::get(cat)){
component.AddButtonOnSlotUpdate(cat);
}
}
};
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::Player_AddButtonOnSlotUpdate=

View File

@ -65,7 +65,7 @@ void Menu::InitializeInventoryWindow(){
auto GetSortedCategories=[](){
std::vector<std::pair<std::string,int>>categories;
for(auto&[category,items]:ITEM_CATEGORIES){
for(auto&category:Item::GetItemCategories()){
if(DATA["ItemCategory"][category].GetString(0)=="!HIDE")continue; //This category is meant to be hidden!
categories.push_back({category,DATA["ItemCategory"][category].GetInt(0)}); //We assume the first value becomes the sort order we wish to use.
}

View File

@ -52,6 +52,7 @@ All rights reserved.
#endif
#include <ranges>
#include "ItemEnchant.h"
#include<algorithm>
INCLUDE_game
INCLUDE_DATA
@ -629,6 +630,7 @@ void Inventory::InsertIntoSortedInv(std::shared_ptr<Item>itemRef){
}
void Inventory::InsertIntoStageInventoryCategory(std::shared_ptr<Item>itemRef,const bool monsterDrop){
if(game->GetCurrentMap().GetMapType()!=Map::MapType::BOSS&&game->GetCurrentMap().GetMapType()!=Map::MapType::DUNGEON)return;
std::string stageInventoryCategory="Stage Loot";
if(monsterDrop){
stageInventoryCategory="Monster Loot";
@ -1522,4 +1524,11 @@ const float Item::CastSize()const{
const float ItemInfo::CastSize()const{
return castSize;
}
std::vector<std::string> Item::GetItemCategories(){
if(ITEM_CATEGORIES.size()!=DATA["ItemCategory"].GetKeys().size())ERR(std::format("WARNING! ITEM_CATEGORIES should be populated before this function is used! Expected {} values in ITEM_CATEGORIES, Current Size: {}",DATA["ItemCategory"].GetKeys().size(),ITEM_CATEGORIES.size()));
static std::vector<std::string>categories{};
if(categories.empty())std::transform(ITEM_CATEGORIES.begin(),ITEM_CATEGORIES.end(),std::back_inserter(categories),[](const auto&pair){return pair.first;});
return categories;
}

View File

@ -187,19 +187,6 @@ class Item{
friend void Merchant::SellItem(std::weak_ptr<Item>,uint32_t amt);
friend class PlayerTests::PlayerTest;
friend class EnchantTests::EnchantTest;
private:
//The amount in the current item stack.
uint32_t amt;
uint8_t enhancementLevel;
ItemInfo*it;
Stats randomizedStats;
bool locked=false;
std::optional<ItemEnchant>enchant{};
void SetAmt(uint32_t newAmt);
static ItemEnhancementFunctionPrimingData enhanceFunctionPrimed;
static int IsBlankStaticCallCounter;
const bool _IsBlank()const;
public:
static const std::string BLANK_ITEM_NAME;
Item();
@ -275,7 +262,21 @@ public:
friend const bool operator==(const IT&lhs,std::shared_ptr<Item>rhs){return operator==(rhs,lhs);};
const Pixel GetDisplayNameColor()const;
static const bool SelectedEquipIsDifferent(const std::weak_ptr<Item>equipAttemptingToEquip,const EquipSlot slotToEquipTo);
static std::vector<std::string>GetItemCategories();
const std::optional<std::string>&FragmentIcon()const;
private:
//The amount in the current item stack.
uint32_t amt;
uint8_t enhancementLevel;
ItemInfo*it;
Stats randomizedStats;
bool locked=false;
std::optional<ItemEnchant>enchant{};
void SetAmt(uint32_t newAmt);
static ItemEnhancementFunctionPrimingData enhanceFunctionPrimed;
static int IsBlankStaticCallCounter;
const bool _IsBlank()const;
};
class Inventory{

View File

@ -83,9 +83,9 @@ Menu::Menu(vf2d pos,vf2d size)
void Menu::InitializeMenus(){
std::for_each(menus.begin(),menus.end(),[](std::pair<MenuType,Menu*>data){delete data.second;}); //EW RAW POINTER.
menus.clear();
for(auto&[key,value]:DATA["ItemCategory"].GetKeys()){
inventoryListeners[key].clear();
merchantInventoryListeners[key].clear();
for(auto&categories:Item::GetItemCategories()){
inventoryListeners[categories].clear();
merchantInventoryListeners[categories].clear();
}
equipStatListeners.clear();
chapterListeners.clear();

View File

@ -49,11 +49,12 @@ All rights reserved.
#include "InputDisplayComponent.h"
#include "GameSettings.h"
#include "Tutorial.h"
#include"InventoryCreator.h"
INCLUDE_game
INCLUDE_ADMIN_MODE
size_t SaveFile::saveFileID=0;
int SaveFile::saveFileID=0;
std::string SaveFile::saveFileName="";
std::string SaveFile::username="";
bool SaveFile::onlineMode=false;
@ -231,6 +232,7 @@ const bool SaveFile::SaveGame(){
}
utils::datafile::INITIAL_SETUP_COMPLETE=true;
#ifdef __EMSCRIPTEN__
if(onlineMode){
std::function<void(std::string_view response)>RetryResponse;
@ -304,10 +306,11 @@ const bool SaveFile::SaveGame(){
#else
game->SetQuitAllowed(true);
#endif
return true;
}
const bool SaveFile::LoadFile(){
const bool SaveFile::_LoadFile(){
utils::datafile loadFile;
std::string loadFilename="save_file_path"_S+std::format("save.{:04}",saveFileID);
@ -359,24 +362,26 @@ const bool SaveFile::LoadFile(){
for(auto&[key,data]:loadFile["Player"]["Base Stats"].GetOrderedKeys()){
game->GetPlayer()->SetBaseStat(key,data.GetReal());
}
if(loadFile.HasProperty("Unlocks")){
for(const auto&[key,data]:loadFile["Unlocks"].GetOrderedKeys()){
Unlock::UnlockArea(key);
if(data.GetValueCount()>1&&data.GetBool(1U)){
auto cps=State_OverworldMap::ConnectionPointsFromString(key);
for(ConnectionPoint&cp:cps)cp.SetVisited();
if(!game->TestingModeEnabled()){
if(loadFile.HasProperty("Unlocks")){
for(const auto&[key,data]:loadFile["Unlocks"].GetOrderedKeys()){
Unlock::UnlockArea(key);
if(data.GetValueCount()>1&&data.GetBool(1U)){
auto cps=State_OverworldMap::ConnectionPointsFromString(key);
for(ConnectionPoint&cp:cps)cp.SetVisited();
}
}
}
if(State_OverworldMap::HasStageMarker(loadFile["Overworld Map Location"].GetString())){
State_OverworldMap::SetStageMarker(loadFile["Overworld Map Location"].GetString());
}
State_OverworldMap::UpdateCurrentConnectionPoint(const_cast<ConnectionPoint&>(State_OverworldMap::GetCurrentConnectionPoint()));
}
if(State_OverworldMap::HasStageMarker(loadFile["Overworld Map Location"].GetString())){
State_OverworldMap::SetStageMarker(loadFile["Overworld Map Location"].GetString());
}
State_OverworldMap::UpdateCurrentConnectionPoint(const_cast<ConnectionPoint&>(State_OverworldMap::GetCurrentConnectionPoint()));
game->SetChapter(loadFile["Chapter"].GetInt());
SaveFile::SetSaveFileName(loadFile["Save Name"].GetString());
game->SetRuntime(loadFile["Game Time"].GetReal());
game->GetPlayer()->RecalculateEquipStats();
if(loadFile.HasProperty("TravelingMerchant"))Merchant::SetTravelingMerchant(loadFile["TravelingMerchant"].GetString());
if(loadFile.HasProperty("TravelingMerchant")&&!game->TestingModeEnabled())Merchant::SetTravelingMerchant(loadFile["TravelingMerchant"].GetString());
if(loadFile.HasProperty("Minimap")){
for(auto&[key,size]:loadFile["Minimap"].GetKeys()){
@ -434,6 +439,11 @@ const void SaveFile::LoadGame(){
game->SetQuitAllowed(false);
const auto TurnOffSavingFlagAndUpdateInventoryUIs{[](){
game->SetQuitAllowed(true);
for(auto&category:Item::GetItemCategories())Menu::InventorySlotsUpdated(category);
}};
#ifdef __EMSCRIPTEN__
if(onlineMode){
Server_GetFile([&](std::string_view response){
@ -441,14 +451,16 @@ const void SaveFile::LoadGame(){
std::ofstream file("save_file_path"_S+std::format("save.{:04}",saveFileID));
file<<response;
file.close();
LoadFile();
_LoadFile();
TurnOffSavingFlagAndUpdateInventoryUIs();
}else{
game->SetQuitAllowed(true);
LOG("WARNING! Could not load save file!");
}
game->SetQuitAllowed(true);
TurnOffSavingFlagAndUpdateInventoryUIs();
});
}else{
emscripten_idb_async_load("/assets",("save_file_path"_S+std::format("save.{:04}",saveFileID)).c_str(),0,[](void*arg,void*data,int length){
emscripten_idb_async_load("/assets",("save_file_path"_S+std::format("save.{:04}",saveFileID)).c_str(),0,[&TurnOffSavingFlagAndUpdateInventoryUIs](void*arg,void*data,int length){
LOG("Loaded Save File "<<saveFileID<<" successfully!");
std::string rawMetadata=(char*)data;
@ -457,16 +469,16 @@ const void SaveFile::LoadGame(){
file<<rawMetadata[i];
}
file.close();
LoadFile();
game->SetQuitAllowed(true);
_LoadFile();
TurnOffSavingFlagAndUpdateInventoryUIs();
},[](void*arg){
LOG("Failed to load Save File "<<saveFileID<<"!");
game->SetQuitAllowed(true);
});
}
#else
LoadFile();
game->SetQuitAllowed(true);
_LoadFile();
TurnOffSavingFlagAndUpdateInventoryUIs();
#endif
}
@ -478,7 +490,7 @@ const void SaveFile::SetSaveFileName(std::string_view saveFileName){
SaveFile::saveFileName=saveFileName;
}
const void SaveFile::SetSaveFileID(size_t saveFileID){
const void SaveFile::SetSaveFileID(int saveFileID){
SaveFile::saveFileID=saveFileID;
}

View File

@ -49,7 +49,7 @@ namespace SaveFileOperation{
};
class SaveFile{
static size_t saveFileID;
static int saveFileID;
static std::string saveFileName;
static std::string username;
static bool onlineMode;
@ -65,8 +65,8 @@ public:
static const void SetUserID(std::string_view userID);
static const bool SaveGame();
static const void LoadGame();
static const bool LoadFile();
static const void SetSaveFileID(size_t saveFileID);
static const bool _LoadFile();
static const void SetSaveFileID(int saveFileID);
static const void SetSaveFileOfflineID_TransitionToOverworldMap();
//Called whenever the save game data is updated.
//WARNING! In Emscripten, this function also opens the Load Menu window!

View File

@ -59,9 +59,6 @@ ConnectionPoint*State_OverworldMap::currentConnectionPoint=nullptr;
State_OverworldMap::State_OverworldMap(){}
void State_OverworldMap::OnStateChange(GameState*prevState){
Inventory::Clear("Monster Loot");
Inventory::Clear("Stage Loot");
Component<MenuComponent>(MenuType::PAUSE,"Return to Camp Button")->SetGrayedOut(false);
SaveFile::SaveGame();

View File

@ -127,9 +127,19 @@ namespace MonsterTests{
class MonsterTest;
}
namespace ItemTests{
class ItemTest;
}
namespace Game{
void LoadFakeLevel(const std::string&mapKeyname,AiL*const game);
};
struct Map{
friend class AiL;
friend class TMXParser;
friend class ItemTests::ItemTest;
friend void Game::LoadFakeLevel(const std::string&mapKeyname,AiL*const game);
enum class MapType{
DUNGEON,
BOSS,

View File

@ -23,7 +23,8 @@ Cherry pick 66a10cee9f874f024633efdc84ebb4432752ab15 from SkeletonFireMage
Snow particles must fade away when Blizzard dies
Blizzard blue area circle should remain during the duration of the attack
Add comments to Effect child Update and Draw declarations
Check for precache locally generated file to re-add to pack first if the date is still current.
The Start button on the overworld map should progress to the item loadout screen.
DEMO

View File

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

View File

@ -1,8 +1,8 @@
INTRO_MAP = 2026-02-25 23:43:58.2817113
HUB = 2026-02-25 23:44:04.3412437
INTRO_MAP = 2026-02-26 21:44:13.5486385
HUB = 2026-02-26 21:44:13.5469761
TEST_MAP = 2026-01-26 20:40:12.7201277
BOSS_1 = 2026-02-25 22:36:51.8834583
WORLD_MAP = 2026-02-25 23:43:58.2792179
BOSS_1 = 2026-02-26 21:44:13.5449625
WORLD_MAP = 2026-02-26 21:44:13.5509566
CAMPAIGN_1_1 = 2026-01-21 20:30:58.3723266
BOSS_1_B = 2024-09-13 21:28:58.8886132
BOSS_2 = 2026-01-21 20:30:58.4312572
@ -14,12 +14,12 @@ BOSS_2_B = 2026-01-21 20:30:58.4322614
CAMPAIGN_1_2 = 2026-01-21 20:30:58.3736269
CAMPAIGN_1_3 = 2024-09-13 21:28:58.8532684
CAMPAIGN_1_5 = 2024-09-13 21:28:58.8557772
CAMPAIGN_4_8 = 2026-02-02 21:11:43.0924363
CAMPAIGN_4_8 = 2026-02-26 21:44:13.5419545
CAMPAIGN_1_6 = 2024-09-13 21:28:58.8570782
CAMPAIGN_1_7 = 2024-09-13 21:28:58.8580547
CAMPAIGN_1_B1 = 2024-09-13 21:28:58.8605592
CAMPAIGN_1_8 = 2024-09-13 21:28:58.8595553
CAMPAIGN_4_5 = 2026-02-02 21:11:24.4671912
CAMPAIGN_4_5 = 2026-02-26 21:44:13.5315825
CAMPAIGN_2_1 = 2026-01-21 20:30:58.3756334
CAMPAIGN_2_2 = 2026-01-21 20:30:58.3769261
CAMPAIGN_2_3 = 2026-01-21 20:30:58.3794325
@ -29,7 +29,7 @@ CAMPAIGN_2_6 = 2026-01-21 20:30:58.3836104
CAMPAIGN_2_7 = 2026-01-21 20:30:58.3846135
CAMPAIGN_2_8 = 2026-01-21 20:30:58.3866133
CAMPAIGN_2_B1 = 2026-01-21 20:30:58.3876140
CAMPAIGN_4_3 = 2026-02-02 21:11:15.8085512
CAMPAIGN_4_3 = 2026-02-26 21:44:13.5235645
CAMPAIGN_3_1 = 2026-01-21 20:30:58.3896136
CAMPAIGN_3_2 = 2026-01-21 20:30:58.3916720
CAMPAIGN_3_3 = 2026-01-21 20:30:58.3937337
@ -39,8 +39,8 @@ CAMPAIGN_3_6 = 2026-01-21 20:30:58.4008008
CAMPAIGN_3_7 = 2026-01-21 20:30:58.4023071
CAMPAIGN_3_8 = 2026-01-21 20:30:58.4049027
CAMPAIGN_3_B1 = 2026-01-21 20:30:58.4336641
CAMPAIGN_4_1 = 2026-02-18 22:01:22.3999658
CAMPAIGN_4_2 = 2026-02-02 21:11:09.6593901
CAMPAIGN_4_4 = 2026-02-02 21:11:20.4201244
CAMPAIGN_4_6 = 2026-02-02 21:11:29.3428325
CAMPAIGN_4_7 = 2026-02-02 21:11:36.4665125
CAMPAIGN_4_1 = 2026-02-26 21:44:13.5188507
CAMPAIGN_4_2 = 2026-02-26 21:44:13.5210572
CAMPAIGN_4_4 = 2026-02-26 21:44:13.5270745
CAMPAIGN_4_6 = 2026-02-26 21:44:13.5342554
CAMPAIGN_4_7 = 2026-02-26 21:44:13.5389009

View File

@ -181,6 +181,12 @@ public:
auto end()const{
return items.end();
}
auto contains(T&key){
return count(key);
}
auto contains(const T&key){
return count(key);
}
auto contains(O&obj){
return std::ranges::contains(items,obj);
}