Clarified and split active zones from base map zones, adding corresponding functions. Changed zone data and map data to use safemaps instead. Separated and fixed map stage name from map names (ids). Fixed story nodes causing an additional map to be generated. Removed STORY and BLACKSMITH map enums. Release Build 12770.
Some checks failed
Emscripten Build / Build_and_Deploy_Web_Build (push) Failing after 3m6s

This commit is contained in:
sigonasr2 2026-02-25 19:04:17 -06:00
parent 2ccf58f938
commit 82ca726ad0
34 changed files with 289 additions and 275 deletions

View File

@ -18,12 +18,6 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="GeometryTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MonsterTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\achievement_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
@ -66,7 +60,7 @@
<ClCompile Include="PlayerTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ItemTests.cpp">
<ClCompile Include="BuffTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="EnchantTests.cpp">
@ -75,10 +69,16 @@
<ClCompile Include="EngineTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="BuffTests.cpp">
<ClCompile Include="FileTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FileTests.cpp">
<ClCompile Include="GeometryTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ItemTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MonsterTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>

View File

@ -81,6 +81,7 @@ namespace BuffTests
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
@ -92,9 +93,10 @@ namespace BuffTests
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
game->MAP_DATA.SetInitialized();
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA.at("CAMPAIGN_1_1")=Map{};
ItemDrop::ClearDrops();
}
#pragma endregion

View File

@ -89,8 +89,9 @@ namespace EnchantTests
testGame->ResetLevelStates();
#pragma region Setup a fake test map and test monster
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA["CAMPAIGN_1_1"]._SetMapData(MapTag{24*24,24*24,24,24});
game->MAP_DATA.at("CAMPAIGN_1_1")._SetMapData(MapTag{24*24,24*24,24,24});
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
@ -108,6 +109,7 @@ namespace EnchantTests
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
game->MAP_DATA.SetInitialized();
}
TEST_METHOD_CLEANUP(EnchantTestCleanup){
testGame->EndGame();

View File

@ -83,6 +83,7 @@ namespace EngineTests
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
#pragma region Setup a fake test map and test monster
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
@ -99,6 +100,7 @@ namespace EngineTests
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
game->MAP_DATA.SetInitialized();
}
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();

View File

@ -82,6 +82,7 @@ namespace FileTests
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
@ -93,9 +94,10 @@ namespace FileTests
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
game->MAP_DATA.SetInitialized();
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA.at("CAMPAIGN_1_1")=Map{};
ItemDrop::ClearDrops();
}
#pragma endregion

View File

@ -89,6 +89,12 @@ namespace Game{
game->ResetPlayerAndChangeClass(cl);
pl=game->GetPlayer(); //The player pointer has been reassigned...
}
inline void LoadLevel(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);
}
}
namespace Test

View File

@ -82,6 +82,7 @@ namespace ItemTests
testGame->ResetLevelStates();
#pragma region Setup a fake test map and test monster
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
@ -95,6 +96,7 @@ namespace ItemTests
testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work.
Menu::themes.SetInitialized();
GFX.SetInitialized();
game->MAP_DATA.SetInitialized();
}
TEST_METHOD_CLEANUP(ItemCleanupTests){
testGame->EndGame();

View File

@ -88,9 +88,11 @@ namespace MonsterTests
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
game->MAP_DATA.SetInitialized();
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
@ -99,9 +101,10 @@ namespace MonsterTests
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
game->MAP_DATA.SetInitialized();
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA.at("CAMPAIGN_1_1")=Map{};
ItemDrop::ClearDrops();
}
#pragma endregion
@ -557,8 +560,7 @@ namespace MonsterTests
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
}
TEST_METHOD(MonsterRunRightTest){
testGame->InitializeLevel("map_path"_S+DATA["Levels"]["TEST_MAP"]["Map File"].GetString(),"TEST_MAP");
testGame->_SetCurrentLevel("TEST_MAP");
Game::LoadLevel("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.
@ -568,8 +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){
testGame->InitializeLevel("map_path"_S+DATA["Levels"]["TEST_MAP"]["Map File"].GetString(),"TEST_MAP");
testGame->_SetCurrentLevel("TEST_MAP");
Game::LoadLevel("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

@ -84,6 +84,7 @@ namespace PlayerTests
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
#pragma region Setup a fake test map and test monster
game->MAP_DATA.Unlock();
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
@ -100,6 +101,7 @@ namespace PlayerTests
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
game->MAP_DATA.SetInitialized();
}
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
@ -800,8 +802,7 @@ namespace PlayerTests
Assert::AreEqual(player->GetMaxHealth()-10,player->GetHealth(),L"Player now takes damage with 0 shield.");
}
TEST_METHOD(PlayerHasteMoveCheck){
testGame->InitializeLevel("map_path"_S+DATA["Levels"]["TEST_MAP"]["Map File"].GetString(),"TEST_MAP");
testGame->_SetCurrentLevel("TEST_MAP");
Game::LoadLevel("TEST_MAP",testGame.get());
player=testGame->GetPlayer();
player->ForceSetPos({12,12});
testKeyboardInput.AddKeybind(Input{InputType::KEY,D});
@ -828,8 +829,7 @@ namespace PlayerTests
}
}
TEST_METHOD(PlayerHasteCooldownCheck){
testGame->InitializeLevel("map_path"_S+DATA["Levels"]["TEST_MAP"]["Map File"].GetString(),"TEST_MAP");
testGame->_SetCurrentLevel("TEST_MAP");
Game::LoadLevel("TEST_MAP",testGame.get());
player=testGame->GetPlayer();
player->ForceSetPos({12,12});
testKeyboardInput.AddKeybind(Input{InputType::KEY,D});

View File

@ -7,8 +7,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Adventures in Lestoria", "A
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Adventures in Lestoria Tests", "Adventures in Lestoria Tests\Adventures in Lestoria Tests.vcxproj", "{11969B7B-3D50-4825-9584-AF01D15B88E0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MapPreloader", "MapPreloader\MapPreloader.vcxproj", "{C19BD586-D45F-48B3-9666-EAD3F92EB526}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -63,30 +61,6 @@ Global
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x64.Build.0 = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x86.ActiveCfg = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x86.Build.0 = Unit Testing|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Debug|x64.ActiveCfg = Debug|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Debug|x64.Build.0 = Debug|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Debug|x86.ActiveCfg = Debug|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Debug|x86.Build.0 = Debug|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Emscripten Debug|x64.ActiveCfg = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Emscripten Debug|x64.Build.0 = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Emscripten Debug|x86.ActiveCfg = Release|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Emscripten Debug|x86.Build.0 = Release|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Emscripten|x64.ActiveCfg = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Emscripten|x64.Build.0 = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Emscripten|x86.ActiveCfg = Release|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Emscripten|x86.Build.0 = Release|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Release Desktop|x64.ActiveCfg = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Release Desktop|x64.Build.0 = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Release Desktop|x86.ActiveCfg = Release|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Release Desktop|x86.Build.0 = Release|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Release|x64.ActiveCfg = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Release|x64.Build.0 = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Release|x86.ActiveCfg = Release|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Release|x86.Build.0 = Release|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Unit Testing|x64.ActiveCfg = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Unit Testing|x64.Build.0 = Release|x64
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Unit Testing|x86.ActiveCfg = Release|Win32
{C19BD586-D45F-48B3-9666-EAD3F92EB526}.Unit Testing|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -334,8 +334,6 @@
"values": [
"Dungeon",
"Boss",
"Story",
"Blacksmith",
"World Map",
"Hub"
],

View File

@ -456,7 +456,7 @@ bool AiL::OnConsoleCommand(const std::string& sCommand){
if(args[0]=="/generatecache"){
ConsoleOut()<<"Generating map caches."<<std::endl;
for(auto&map:MAP_DATA){
if(!map.GetMapName().starts_with("STORY"))TileGroupDataFile::AddToMapCacheQueue(map.GetMapName());
if(!map.GetMapKeyName().starts_with("STORY"))TileGroupDataFile::AddToMapCacheQueue(map.GetMapKeyName());
}
GameState::ChangeState(States::GAME_RUN);
}else{
@ -1063,8 +1063,8 @@ void AiL::RenderWorld(float fElapsedTime){
auto foregroundEffectsLower{foregroundEffects|std::views::filter([](const std::unique_ptr<Effect>&effect){return !effect->OnUpperLevel();})};
auto backgroundEffectsUpper{backgroundEffects|std::views::filter([](const std::unique_ptr<Effect>&effect){return effect->OnUpperLevel();})};
auto backgroundEffectsLower{backgroundEffects|std::views::filter([](const std::unique_ptr<Effect>&effect){return !effect->OnUpperLevel();})};
auto upperEndZones{ZONE_LIST["EndZone"]|std::views::filter([](const ZoneData&zone){return zone.isUpper;})};
auto endZones{ZONE_LIST["EndZone"]|std::views::filter([](const ZoneData&zone){return !zone.isUpper;})};
auto upperEndZones{GetActiveZonesForCurrentMap()["EndZone"]|std::views::filter([](const ZoneData&zone){return zone.isUpper;})};
auto endZones{GetActiveZonesForCurrentMap()["EndZone"]|std::views::filter([](const ZoneData&zone){return !zone.isUpper;})};
#pragma endregion
auto RenderPlayer=[&](vf2d pos,vf2d scale){
@ -1123,7 +1123,7 @@ void AiL::RenderWorld(float fElapsedTime){
}
};
auto RenderZone=[&](geom2d::rect<int>&zone){
auto RenderZone=[&](const geom2d::rect<int>&zone){
game->SetDecalMode(DecalMode::ADDITIVE);
Pixel ringColor={128,128,128,uint8_t(abs(sin(game->levelTime))*255)};
Decal*ringDecal=GFX["finishring_green.png"].Decal();
@ -1322,7 +1322,7 @@ void AiL::RenderWorld(float fElapsedTime){
}
#pragma endregion
for(ZoneData&zone:endZones){
for(const ZoneData&zone:endZones){
RenderZone(zone.zone);
}
auto RenderPrecastTargetingIndicator=[&](){
@ -1635,7 +1635,7 @@ void AiL::RenderWorld(float fElapsedTime){
}
#pragma endregion
for(ZoneData&zone:upperEndZones){
for(const ZoneData&zone:upperEndZones){
RenderZone(zone.zone);
}
@ -2281,127 +2281,10 @@ 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();
for(auto&[key,size]:DATA["Levels"][map]){
if(key.starts_with("Loot")){
datafile data=DATA["Levels"][map][key];
int pctChance=100;
if(data.GetValueCount()>3)pctChance=data.GetInt(3U);
MAP_DATA[map].stageLoot.push_back(ItemMapData(data.GetString(0U),data.GetInt(1U),data.GetInt(2U),pctChance));
}else
if(key.starts_with("Skip Loadout Selection")){
MAP_DATA[map].skipLoadoutScreen=DATA["Levels"][map]["Skip Loadout Selection"].GetBool();
}
}
for(XMLTag&tag:MAP_DATA[map].TilesetData){
size_t slashMarkerSourceDir = tag.data["source"].find_last_of('/');
std::string baseSourceDir=tag.data["source"].substr(slashMarkerSourceDir+1);
if(MAP_TILESETS.find("assets/maps/"+baseSourceDir)==MAP_TILESETS.end()){
TSXParser tileset(baseDir+tag.data["source"]);
Renderable*r=NEW Renderable();
if(tileset.GetData().tilewidth==0||tileset.GetData().tileheight==0)ERR(std::format("WARNING! Failed to load map {}! Found a tileset {} with a width of {} and height of {}. Zero values are not allowed!",map,"assets/maps/"+baseSourceDir,tileset.GetData().tilewidth,tileset.GetData().tileheight));
MAP_TILESETS["assets/maps/"+baseSourceDir].tilewidth=tileset.GetData().tilewidth;
MAP_TILESETS["assets/maps/"+baseSourceDir].tileheight=tileset.GetData().tileheight;
MAP_TILESETS["assets/maps/"+baseSourceDir].tileset=r;
MAP_TILESETS["assets/maps/"+baseSourceDir].foregroundTiles=tileset.GetData().ForegroundTileData;
MAP_TILESETS["assets/maps/"+baseSourceDir].upperForegroundTiles=tileset.GetData().UpperForegroundTileData;
MAP_TILESETS["assets/maps/"+baseSourceDir].collision=tileset.GetData().CollisionData;
MAP_TILESETS["assets/maps/"+baseSourceDir].staircaseTiles=tileset.GetData().StaircaseData;
MAP_TILESETS["assets/maps/"+baseSourceDir].animationData=tileset.GetData().AnimationData;
MAP_TILESETS["assets/maps/"+baseSourceDir].reflectiveData=tileset.GetData().ReflectiveData;
MAP_TILESETS["assets/maps/"+baseSourceDir].tileRepeatData=tileset.GetData().TileRepeatData;
MAP_TILESETS["assets/maps/"+baseSourceDir].isTerrain=tileset.GetData().isTerrain;
MAP_TILESETS["assets/maps/"+baseSourceDir].collision.SetInitialized();
LOG("assets/maps/"+baseSourceDir<<" Animation Data Size: "<<MAP_TILESETS["assets/maps/"+baseSourceDir].animationData.size());
std::string mapPath="assets/maps/"+tileset.GetData().ImageData.data["source"];
if(gamepack.Loaded()){
r->Load(mapPath,&gamepack);
}else
if(std::filesystem::exists(mapPath)){
r->Load(mapPath);
if("GENERATE_GAMEPACK"_B){
gamepack.AddFile(mapPath);
}
}else{
LOG("WARNING! "<<mapPath<<" does not exist, auto-generating mock-up texture");
r->Create(tileset.GetData().imagewidth,tileset.GetData().imageheight);
SetDrawTarget(r->Sprite());
int tileXCount=tileset.GetData().imagewidth/tileset.GetData().tilewidth;
int tileYCount=tileset.GetData().imageheight/tileset.GetData().tileheight;
vi2d tileSize={tileset.GetData().tilewidth,tileset.GetData().tileheight};
const int colorCombinations=255*6;
for(int y=0;y<tileYCount;y++){
for(int x=0;x<tileXCount;x++){
int colorCycleIndex=(y*255+x)%colorCombinations;
Pixel tileCol=WHITE;
if(colorCycleIndex<255){
tileCol={uint8_t(colorCycleIndex%255),0,0};
}else
if(colorCycleIndex<255*2){
tileCol={0,uint8_t(colorCycleIndex%255),0};
}else
if(colorCycleIndex<255*3){
tileCol={0,0,uint8_t(colorCycleIndex%255)};
}else
if(colorCycleIndex<255*4){
tileCol={uint8_t(colorCycleIndex%255),uint8_t(colorCycleIndex%255),0};
}else
if(colorCycleIndex<255*5){
tileCol={0,uint8_t(colorCycleIndex%255),uint8_t(colorCycleIndex%255)};
}else{
tileCol={uint8_t(colorCycleIndex%255),0,uint8_t(colorCycleIndex%255)};
}
FillRect(vi2d{x,y}*tileSize,tileSize,tileCol);
DrawRect(vi2d{x,y}*tileSize,tileSize,GREY);
DrawString(vi2d{x,y}*tileSize+vi2d{1,1},std::to_string(y*255+x),DARK_GREY);
}
}
SetDrawTarget(nullptr);
r->Decal()->Update();
}
ComputeModeColors(MAP_TILESETS["assets/maps/"+baseSourceDir]);
}
}
if(MAP_DATA[map].MapData.optimized||MAP_DATA[map].MapData.provideOptimization){
LOG("Generating optimized map for Map "<<map);
MAP_DATA[map].optimizedTile=NEW Renderable();
MAP_DATA[map].optimizedTile->Create(MAP_DATA[map].MapData.width*MAP_DATA[map].MapData.tilewidth,MAP_DATA[map].MapData.height*MAP_DATA[map].MapData.tileheight);
SetDrawTarget(MAP_DATA[map].optimizedTile->Sprite());
Pixel::Mode prevMode=GetPixelMode();
SetPixelMode(Pixel::Mode::MASK);
Clear(BLANK);
for(int y=0;y<MAP_DATA[map].MapData.height;y++){
for(int x=0;x<MAP_DATA[map].MapData.width;x++){
for(auto&layer:MAP_DATA[map].LayerData){
int tileID=layer.tiles[y][x]-1;
if(tileID!=-1){
TilesheetData tileSheet=GetTileSheet(map,tileID);
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
int tileSheetHeight=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->height/MAP_TILESETS.at(tileSheet.tilesetName).tileheight;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int tileSheetX=tileSheetIndex%tileSheetWidth;
int tileSheetY=tileSheetIndex/tileSheetWidth;
vi2d pos=vi2d{x,y}*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
DrawPartialSprite(pos,MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite(),vi2d{tileSheetX,tileSheetY}*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,{MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,MAP_TILESETS.at(tileSheet.tilesetName).tileheight});
}
}
}
}
SetPixelMode(prevMode);
MAP_DATA[map].optimizedTile->Decal()->Update();
SetDrawTarget(nullptr);
if(!MAP_DATA[map].MapData.provideOptimization){
MAP_DATA[map].LayerData.clear();
LOG(" Clearing Layer Data...");
}
}
MAP_DATA.at(map).name=map;
if(TestingModeEnabled())MAP_DATA.SetInitialized();
}
void AiL::LoadLevel(MapName map,MusicChange changeMusic){
@ -2418,7 +2301,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
#pragma region Reset all data (Loading phase 1)
LoadingScreen::AddPhase([&](){
if(game->MAP_DATA.count(GetCurrentLevel())==0)ERR(std::format("WARNING! Could not load map {}! Does not exist! Refer to levels.txt for valid maps.",map));
if(game->MAP_DATA[GetCurrentLevel()].GetMapType()==Map::MapType::HUB&&GameState::STATE!=GameState::states[States::GAME_HUB]&&!TileGroupDataFile::HasMapsInCacheQueue())ERR("WARNING! A hub level should only be initiated in the GAME_HUB game state!");
if(game->MAP_DATA[GetCurrentLevel()].GetMapType()==Map::MapType::HUB&&GameState::STATE!=GameState::states[States::GAME_HUB]&&!game->MapCacheIsRunning)ERR("WARNING! A hub level should only be initiated in the GAME_HUB game state!");
#pragma region Reset Environmental Audio
for(EnvironmentalAudio&audio:MAP_DATA[previousLevel].environmentalAudioData){
@ -2434,6 +2317,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
)
ZONE_LIST=game->MAP_DATA[game->GetCurrentLevel()].ZoneData;
ZONE_LIST.SetInitialized();
return true;
});
#pragma endregion
@ -2475,7 +2359,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
const int spawnerId=spawnData.ObjectData.GetInteger("id");
if(SPAWNER_LIST.count(spawnerId))ERR(std::format("WARNING! Spawner ID {} for Map {} is somehow duplicated! THIS SHOULD NOT BE HAPPENING!",spawnData.ObjectData.GetInteger("id"),GetCurrentMapDisplayName()))
if(SPAWNER_LIST.count(spawnerId))ERR(std::format("WARNING! Spawner ID {} for Map {} is somehow duplicated! THIS SHOULD NOT BE HAPPENING!",spawnData.ObjectData.GetInteger("id"),GetCurrentMapStageName()))
SPAWNER_LIST[spawnerId]=MonsterSpawner{{spawnData.ObjectData.GetFloat("x"),spawnData.ObjectData.GetFloat("y")},spawnerRadius*2,monster_list,spawnData.upperLevel,spawnData.bossNameDisplay};
if(IdsToDisable.count(spawnerId))SPAWNER_LIST.at(spawnerId).SetTriggered(true,false); //Force this spawner to be deactivated because it is in a spawn controller.
}
@ -2487,12 +2371,8 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
#pragma region Identify Upper Foreground Tiles (Loading phase 3)
LoadingScreen::AddPhase([&](){
auto GetUpperZones=[&](){
for(auto&zoneSet:MAP_DATA[GetCurrentLevel()].ZoneData){
if(zoneSet.first=="UpperZone"){ //We are interested in all upper zones.
return zoneSet.second;
}
}
return std::vector<ZoneData>{};
if(MAP_DATA[GetCurrentLevel()].ZoneData.count("UpperZone")){return MAP_DATA[GetCurrentLevel()].ZoneData["UpperZone"];}
else return std::vector<ZoneData>{};
};
for(ZoneData&zone:GetUpperZones()){
int zoneX=zone.zone.pos.x/game->GetCurrentMapData().tilewidth; //snap to grid
@ -3388,6 +3268,12 @@ void AiL::InitializeLevels(){
InitializeLevel("map_path"_S+DATA["Levels"][key]["Map File"].GetString(),key);
}
MAP_DATA.SetInitialized();
for(auto&map:MAP_DATA){
SetupLevel(map);
}
std::set<std::string>cpNames;
for(ConnectionPoint&cp:State_OverworldMap::connections){
if(cpNames.count(cp.name)>0)ERR(std::format("WARNING! More than one connection point has the same name: {} THIS IS NOT ALLOWED!",cp.name));
@ -3400,10 +3286,10 @@ void AiL::InitializeLevels(){
cp.levelDataExists=true;
}
if(MAP_DATA.count(cp.map)){
MAP_DATA[cp.map].name=cp.name;
MAP_DATA[cp.map].stageName=cp.name;
//Boss arena map zone check.
if(MAP_DATA[cp.map].GetMapType()==Map::MapType::BOSS&&MAP_DATA[cp.map].GetZones().at("BossArena").size()==0)ERR(std::format("WARNING! Map {} doesn't have a boss arena region defined! Add an object of class BossArena to the map.",MAP_DATA[cp.map].GetMapDisplayName()));
if(MAP_DATA[cp.map].GetMapType()==Map::MapType::BOSS&&MAP_DATA[cp.map].GetZones().at("BossArena").size()==0)ERR(std::format("WARNING! Map {} doesn't have a boss arena region defined! Add an object of class BossArena to the map.",MAP_DATA[cp.map].GetMapStageName()));
for(std::string spawn:MAP_DATA[cp.map].spawns){
cp.spawns.push_back(MONSTER_DATA.at(spawn).GetDisplayName());
@ -3861,9 +3747,9 @@ void AiL::ValidateGameStatus(){
}
}
auto MapStoryDataExists = [&](const std::string&mapName){
return MAP_DATA.count(MapName(mapName))>0||
VisualNovel::storyLevelData.count(mapName)>0;
auto MapStoryDataExists = [&](const std::string&levelName){
return MAP_DATA.count(MapName(levelName))>0||
VisualNovel::storyLevelData.count(levelName)>0;
};
if(!MapStoryDataExists("NPCs.Sherman.Potion Crafting Unlock Condition"_S))ERR(std::format("WARNING! Sherman's Potion Crafting Unlock Condition: {} is not a valid map!","NPCs.Sherman.Potion Crafting Unlock Condition"_S))
@ -3882,7 +3768,7 @@ void AiL::ValidateGameStatus(){
monsterCounts[monsterName]++;
}
}
LOG("Spawns Report for "<<map.GetMapDisplayName()<<":");
LOG("Spawns Report for "<<map.GetMapStageName()<<":");
for(auto&[monster,count]:monsterCounts){
LOG("\t"<<count<<"x "<<monster);
}
@ -4282,22 +4168,23 @@ const Pixel&AiL::GetWorldColor()const{
return worldColor;
}
const std::unordered_map<std::string,std::vector<::ZoneData>>&AiL::GetZones(const std::string_view mapName)const{
if(GetCurrentMapDisplayName()==mapName)return GetZones();
return MAP_DATA.at(std::string(mapName)).ZoneData;
}
const std::unordered_map<std::string,std::vector<::ZoneData>>&AiL::GetZones()const{
const safeunorderedmap<std::string,std::vector<::ZoneData>>&AiL::GetActiveZonesForCurrentMap()const{
return ZONE_LIST;
}
void AiL::AddZone(const std::string_view zoneName,const ZoneData&zone){
if(ZONE_LIST.count(std::string(zoneName))==0)ERR(std::format("WARNING! Trying to add non-existent Zone Key {} to zone list of map {}. THIS IS NOT ALLOWED!",zoneName,std::string(GetCurrentMapName())));
ZONE_LIST[std::string(zoneName)].push_back(zone);
const safeunorderedmap<std::string,std::vector<::ZoneData>>&AiL::GetZones(const std::string&mapKey)const{
return MAP_DATA[mapKey].ZoneData;
}
const std::string_view AiL::GetCurrentMapDisplayName()const{
return GetCurrentMap().GetMapDisplayName();
void AiL::AddActiveZoneToCurrentMap(const std::string&zoneName,const ZoneData&zone){
ZONE_LIST.Unlock();
if(ZONE_LIST.count(zoneName)==0)ERR(std::format("WARNING! Trying to add non-existent Zone Key {} to zone list of map {}. THIS IS NOT ALLOWED!",zoneName,GetCurrentMapName()));
ZONE_LIST[zoneName].emplace_back(zone);
ZONE_LIST.SetInitialized();
}
const std::string_view AiL::GetCurrentMapStageName()const{
return GetCurrentMap().GetMapStageName();
}
const uint8_t AiL::BossEncounterMobCount()const{
@ -4777,19 +4664,142 @@ void AiL::PrecacheNewLevels(){
const std::string precacheConfigurationFilepath{std::format("{}{}","config_path"_S,"map_precache_timestamp_config"_S)};
if(std::filesystem::exists(precacheConfigurationFilepath))datafile::Read(cacheTimestampsData,precacheConfigurationFilepath);
for(auto&map:MAP_DATA){
const std::string mapFileName{std::format("assets/Campaigns/{}",DATA["Levels"][map.GetMapName()]["Map File"].GetString())};
const std::string mapCacheFileName{std::format("assets/cache/foregroundTileData_{}.data",map.GetMapName())};
const std::string mapFileName{std::format("assets/Campaigns/{}",DATA["Levels"][map.GetMapKeyName()]["Map File"].GetString())};
const std::string mapCacheFileName{std::format("assets/cache/foregroundTileData_{}.data",map.GetMapKeyName())};
const std::string lastWriteTimestamp{std::format("{}",std::filesystem::last_write_time(mapFileName))};
const bool PrecacheFileExists{game->gamepack.FileExists(mapCacheFileName)};
const bool CacheTimestampDiffersFromLastWriteTime{!cacheTimestampsData.HasProperty(map.GetMapName())||cacheTimestampsData[map.GetMapName()].GetString()!=lastWriteTimestamp};
const bool MapCacheOutOfDate{!cacheTimestampsData.HasProperty(map.GetMapName())||CacheTimestampDiffersFromLastWriteTime};
const bool CacheTimestampDiffersFromLastWriteTime{!cacheTimestampsData.HasProperty(map.GetMapKeyName())||cacheTimestampsData[map.GetMapKeyName()].GetString()!=lastWriteTimestamp};
const bool MapCacheOutOfDate{!cacheTimestampsData.HasProperty(map.GetMapKeyName())||CacheTimestampDiffersFromLastWriteTime};
if(!PrecacheFileExists||MapCacheOutOfDate){
cacheTimestampsData[map.GetMapName()].SetString(lastWriteTimestamp);
TileGroupDataFile::AddToMapCacheQueue(map.GetMapName());
cacheTimestampsData[map.GetMapKeyName()].SetString(lastWriteTimestamp);
TileGroupDataFile::AddToMapCacheQueue(map.GetMapKeyName());
}
}
datafile::Write(cacheTimestampsData,precacheConfigurationFilepath);
datafile::INITIAL_SETUP_COMPLETE=true;
}
void AiL::SetupLevel(Map&map){
datafile mapData{DATA["Levels"][map.GetMapKeyName()]};
for(auto&[key,size]:mapData){
if(key.starts_with("Loot")){
datafile data=DATA["Levels"][map.GetMapKeyName()][key];
int pctChance=100;
if(data.GetValueCount()>3)pctChance=data.GetInt(3U);
MAP_DATA[map.GetMapKeyName()].stageLoot.push_back(ItemMapData(data.GetString(0U),data.GetInt(1U),data.GetInt(2U),pctChance));
}else
if(key.starts_with("Skip Loadout Selection")){
MAP_DATA[map.GetMapKeyName()].skipLoadoutScreen=DATA["Levels"][map.GetMapKeyName()]["Skip Loadout Selection"].GetBool();
}
}
for(XMLTag&tag:MAP_DATA[map.GetMapKeyName()].TilesetData){
size_t slashMarkerSourceDir = tag.data["source"].find_last_of('/');
std::string baseSourceDir=tag.data["source"].substr(slashMarkerSourceDir+1);
if(MAP_TILESETS.find("assets/maps/"+baseSourceDir)==MAP_TILESETS.end()){
TSXParser tileset("map_path"_S+tag.data["source"]);
Renderable*r=NEW Renderable();
if(tileset.GetData().tilewidth==0||tileset.GetData().tileheight==0)ERR(std::format("WARNING! Failed to load map {}! Found a tileset {} with a width of {} and height of {}. Zero values are not allowed!",map.GetMapStageName(),"assets/maps/"+baseSourceDir,tileset.GetData().tilewidth,tileset.GetData().tileheight));
MAP_TILESETS["assets/maps/"+baseSourceDir].tilewidth=tileset.GetData().tilewidth;
MAP_TILESETS["assets/maps/"+baseSourceDir].tileheight=tileset.GetData().tileheight;
MAP_TILESETS["assets/maps/"+baseSourceDir].tileset=r;
MAP_TILESETS["assets/maps/"+baseSourceDir].foregroundTiles=tileset.GetData().ForegroundTileData;
MAP_TILESETS["assets/maps/"+baseSourceDir].upperForegroundTiles=tileset.GetData().UpperForegroundTileData;
MAP_TILESETS["assets/maps/"+baseSourceDir].collision=tileset.GetData().CollisionData;
MAP_TILESETS["assets/maps/"+baseSourceDir].staircaseTiles=tileset.GetData().StaircaseData;
MAP_TILESETS["assets/maps/"+baseSourceDir].animationData=tileset.GetData().AnimationData;
MAP_TILESETS["assets/maps/"+baseSourceDir].reflectiveData=tileset.GetData().ReflectiveData;
MAP_TILESETS["assets/maps/"+baseSourceDir].tileRepeatData=tileset.GetData().TileRepeatData;
MAP_TILESETS["assets/maps/"+baseSourceDir].isTerrain=tileset.GetData().isTerrain;
MAP_TILESETS["assets/maps/"+baseSourceDir].collision.SetInitialized();
LOG("assets/maps/"+baseSourceDir<<" Animation Data Size: "<<MAP_TILESETS["assets/maps/"+baseSourceDir].animationData.size());
std::string mapPath="assets/maps/"+tileset.GetData().ImageData.data["source"];
if(gamepack.Loaded()){
r->Load(mapPath,&gamepack);
}else
if(std::filesystem::exists(mapPath)){
r->Load(mapPath);
if("GENERATE_GAMEPACK"_B){
gamepack.AddFile(mapPath);
}
}else{
LOG("WARNING! "<<mapPath<<" does not exist, auto-generating mock-up texture");
r->Create(tileset.GetData().imagewidth,tileset.GetData().imageheight);
SetDrawTarget(r->Sprite());
int tileXCount=tileset.GetData().imagewidth/tileset.GetData().tilewidth;
int tileYCount=tileset.GetData().imageheight/tileset.GetData().tileheight;
vi2d tileSize={tileset.GetData().tilewidth,tileset.GetData().tileheight};
const int colorCombinations=255*6;
for(int y=0;y<tileYCount;y++){
for(int x=0;x<tileXCount;x++){
int colorCycleIndex=(y*255+x)%colorCombinations;
Pixel tileCol=WHITE;
if(colorCycleIndex<255){
tileCol={uint8_t(colorCycleIndex%255),0,0};
}else
if(colorCycleIndex<255*2){
tileCol={0,uint8_t(colorCycleIndex%255),0};
}else
if(colorCycleIndex<255*3){
tileCol={0,0,uint8_t(colorCycleIndex%255)};
}else
if(colorCycleIndex<255*4){
tileCol={uint8_t(colorCycleIndex%255),uint8_t(colorCycleIndex%255),0};
}else
if(colorCycleIndex<255*5){
tileCol={0,uint8_t(colorCycleIndex%255),uint8_t(colorCycleIndex%255)};
}else{
tileCol={uint8_t(colorCycleIndex%255),0,uint8_t(colorCycleIndex%255)};
}
FillRect(vi2d{x,y}*tileSize,tileSize,tileCol);
DrawRect(vi2d{x,y}*tileSize,tileSize,GREY);
DrawString(vi2d{x,y}*tileSize+vi2d{1,1},std::to_string(y*255+x),DARK_GREY);
}
}
SetDrawTarget(nullptr);
r->Decal()->Update();
}
ComputeModeColors(MAP_TILESETS["assets/maps/"+baseSourceDir]);
}
}
if(MAP_DATA[map.GetMapKeyName()].MapData.optimized||MAP_DATA[map.GetMapKeyName()].MapData.provideOptimization){
LOG("Generating optimized map for Map "<<map);
MAP_DATA[map.GetMapKeyName()].optimizedTile=NEW Renderable();
MAP_DATA[map.GetMapKeyName()].optimizedTile->Create(MAP_DATA[map.GetMapKeyName()].MapData.width*MAP_DATA[map.GetMapKeyName()].MapData.tilewidth,MAP_DATA[map.GetMapKeyName()].MapData.height*MAP_DATA[map.GetMapKeyName()].MapData.tileheight);
SetDrawTarget(MAP_DATA[map.GetMapKeyName()].optimizedTile->Sprite());
Pixel::Mode prevMode=GetPixelMode();
SetPixelMode(Pixel::Mode::MASK);
Clear(BLANK);
for(int y=0;y<MAP_DATA[map.GetMapKeyName()].MapData.height;y++){
for(int x=0;x<MAP_DATA[map.GetMapKeyName()].MapData.width;x++){
for(auto&layer:MAP_DATA[map.GetMapKeyName()].LayerData){
int tileID=layer.tiles[y][x]-1;
if(tileID!=-1){
TilesheetData tileSheet=GetTileSheet(map.GetMapKeyName(),tileID);
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
int tileSheetHeight=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->height/MAP_TILESETS.at(tileSheet.tilesetName).tileheight;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int tileSheetX=tileSheetIndex%tileSheetWidth;
int tileSheetY=tileSheetIndex/tileSheetWidth;
vi2d pos=vi2d{x,y}*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
DrawPartialSprite(pos,MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite(),vi2d{tileSheetX,tileSheetY}*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,{MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,MAP_TILESETS.at(tileSheet.tilesetName).tileheight});
}
}
}
}
SetPixelMode(prevMode);
MAP_DATA[map.GetMapKeyName()].optimizedTile->Decal()->Update();
SetDrawTarget(nullptr);
if(!MAP_DATA[map.GetMapKeyName()].MapData.provideOptimization){
MAP_DATA[map.GetMapKeyName()].LayerData.clear();
LOG(" Clearing Layer Data...");
}
}
}

View File

@ -188,6 +188,7 @@ public:
geom2d::rect<float>NO_COLLISION={{0.f,0.f,},{0.f,0.f}};
TileTransformedView view;
void InitializeLevel(std::string mapFile,MapName map);
void SetupLevel(Map&map);
void LoadLevel(MapName map,MusicChange changeMusic=PLAY_LEVEL_MUSIC);
void HandleUserInput(float fElapsedTime);
void UpdateCamera(float fElapsedTime);
@ -273,7 +274,7 @@ public:
const Map&GetCurrentMap()const;
const MapTag&GetCurrentMapData()const;
const MapName&GetCurrentMapName()const;
const std::string_view GetCurrentMapDisplayName()const;
const std::string_view GetCurrentMapStageName()const;
int GetCurrentChapter();
void SetChapter(int chapter);
const std::weak_ptr<Item>GetLoadoutItem(int slot); //Slot range is 0-2.
@ -296,11 +297,11 @@ public:
void SetWorldColorFunc(std::function<Pixel(vi2d)>func);
void SetWorldColor(Pixel worldCol);
const Pixel&GetWorldColor()const;
//Returns the zones in the current stage
const std::unordered_map<std::string,std::vector<::ZoneData>>&GetZones()const;
//Returns the active zones in the current stage (Originally populated by the zone data inside of the stage's map data)
const safeunorderedmap<std::string,std::vector<::ZoneData>>&GetActiveZonesForCurrentMap()const;
//Returns the zones of any given stage
const std::unordered_map<std::string,std::vector<::ZoneData>>&GetZones(const std::string_view mapName)const;
void AddZone(const std::string_view zoneName,const ZoneData&zone);
const safeunorderedmap<std::string,std::vector<::ZoneData>>&GetZones(const std::string&mapName)const;
void AddActiveZoneToCurrentMap(const std::string&zoneName,const ZoneData&zone);
//Returns the last time the mouse was moved or interacted with.
const float LastMouseMovement()const;
const bool GameInitialized()const;
@ -395,7 +396,7 @@ private:
int playerShieldDisplayAmt{};
Pixel worldColor=WHITE;
std::function<Pixel(vi2d)>worldColorFunc=[](vi2d pos){return WHITE;};
std::unordered_map<std::string,std::vector<::ZoneData>>ZONE_LIST;
safeunorderedmap<std::string,std::vector<::ZoneData>>ZONE_LIST;
float lastMouseMovement=0.f; //Amount of time since the last time the cursor was moved or interacted with.
vi2d lastMousePos={};
bool gameInitialized=false;

View File

@ -39,6 +39,7 @@ All rights reserved.
#include "EnvironmentalAudio.h"
#include "Audio.h"
#include "AdventuresInLestoria.h"
#include"VisualNovel.h"
INCLUDE_game
INCLUDE_DATA
@ -80,6 +81,7 @@ void EnvironmentalAudio::Deactivate(){
activated=false;
}
void EnvironmentalAudio::UpdateEnvironmentalAudio(){
if(GameState::GetCurrentState()==States::STORY)return;
for(const EnvironmentalAudio&aud:game->GetCurrentMap().GetEnvironmentalAudio()){
EnvironmentalAudio&audio=const_cast<EnvironmentalAudio&>(aud);
audio.Update();

View File

@ -186,7 +186,7 @@ DEFINE_STRATEGY(GHOST_OF_PIRATE_CAPTAIN)
if(!OnLastCursePhase)game->AddEffect(std::make_unique<FlipCoinEffect>(Oscillator<vf2d>{m.GetPos(),m.V(A::TOSS_COIN_TARGET),1.f/m.F(A::TOSS_COIN_WAIT_TIMER)/2.f},ConfigFloat("Coin Toss Rise Amount"),m.F(A::TOSS_COIN_WAIT_TIMER),"coin.png",m.OnUpperLevel(),3.f));
#pragma region Determine a hiding spot
const auto&hidingSpots{game->GetZones().at("Hiding Spot")};
const auto&hidingSpots{game->GetActiveZonesForCurrentMap().at("Hiding Spot")};
if(hidingSpots.size()==0)ERR("WARNING! Could not find any zones with the name \"Hiding Spot\" on the map!! THIS SHOULD NOT BE HAPPENING!")
m.V(A::HIDING_POS)=hidingSpots[util::random()%hidingSpots.size()].zone.middle();
#pragma endregion

View File

@ -55,7 +55,7 @@ ItemDrop::ItemDrop(const ItemInfo*item,const vf2d pos,const bool isUpper)
:item(item),pos(pos),upperLevel(isUpper){
const bool HasBossArenaBounds=game->GetCurrentMap().GetMapType()==Map::MapType::BOSS;
if(HasBossArenaBounds){
const geom2d::rect<int>arenaBounds=game->GetZones().at("BossArena")[0].zone;
const geom2d::rect<int>arenaBounds=game->GetActiveZonesForCurrentMap()["BossArena"][0].zone;
this->pos.x=std::clamp(this->pos.x,float(arenaBounds.pos.x),float(arenaBounds.pos.x+arenaBounds.size.x));
this->pos.y=std::clamp(this->pos.y,float(arenaBounds.pos.y),float(arenaBounds.pos.y+arenaBounds.size.y));
}
@ -124,7 +124,7 @@ void ItemDrop::UpdateDrops(float fElapsedTime){
#pragma region Handle Upper/Lower Level Zone Intersecting
if(drop.speed.mag()>0){
const std::unordered_map<std::string,std::vector<ZoneData>>&zoneData=game->GetZones(game->GetCurrentLevel());
const safeunorderedmap<std::string,std::vector<ZoneData>>&zoneData=game->GetZones(game->GetCurrentLevel());
for(const ZoneData&upperLevelZone:zoneData.at("UpperZone")){
if(geom2d::overlaps(upperLevelZone.zone,drop.pos)){
drop.upperLevel=true;

View File

@ -140,8 +140,8 @@ void Minimap::Reset(){
}
}
if(game->GetZones().count("EndZone")){
for(const ZoneData&zone:game->GetZones().at("EndZone")){
if(game->GetActiveZonesForCurrentMap().count("EndZone")){
for(const ZoneData&zone:game->GetActiveZonesForCurrentMap()["EndZone"]){
vf2d ringPos=zone.zone.pos/game->GetCurrentMapData().TileSize;
vf2d ringSize=zone.zone.size/game->GetCurrentMapData().TileSize;
vf2d ringCenter=ringPos+ringSize/2;

View File

@ -205,7 +205,7 @@ bool Monster::_SetX(float x,const bool monsterInvoked){
bool insideArenaBounds=true;
#pragma region Calculate Arena Bounds check for Bosses
if(isBoss){
const geom2d::rect<int>arenaBounds=game->GetZones().at("BossArena")[0].zone;
const geom2d::rect<int>arenaBounds=game->GetActiveZonesForCurrentMap()["BossArena"][0].zone;
if(!geom2d::contains(arenaBounds,newPos)){
insideArenaBounds=false;
}
@ -245,7 +245,7 @@ bool Monster::_SetY(float y,const bool monsterInvoked){
bool insideArenaBounds=true;
#pragma region Calculate Arena Bounds check for Bosses
if(isBoss){
const geom2d::rect<int>arenaBounds=game->GetZones().at("BossArena")[0].zone;
const geom2d::rect<int>arenaBounds=game->GetActiveZonesForCurrentMap()["BossArena"][0].zone;
if(!geom2d::contains(arenaBounds,newPos)){
insideArenaBounds=false;
}
@ -746,7 +746,7 @@ bool Monster::SetPos(vf2d pos){
return resultX||resultY;
}
void Monster::Moved(){
const std::unordered_map<std::string,std::vector<ZoneData>>&zoneData=game->GetZones(game->GetCurrentLevel());
const safeunorderedmap<std::string,std::vector<ZoneData>>&zoneData=game->GetZones(game->GetCurrentLevel());
for(const ZoneData&upperLevelZone:zoneData.at("UpperZone")){
if(geom2d::overlaps(upperLevelZone.zone,pos)){
upperLevel=true;
@ -1139,13 +1139,13 @@ void Monster::OnDeath(){
}else{
ZoneData exitRing{geom2d::rect<int>{vi2d{GetPos()-vf2d{"boss_spawn_ring_radius"_F,"boss_spawn_ring_radius"_F}},vi2d{"boss_spawn_ring_radius"_I*2,"boss_spawn_ring_radius"_I*2}},OnUpperLevel()};
const geom2d::rect<int>arenaBounds=game->GetZones().at("BossArena")[0].zone;
const geom2d::rect<int>arenaBounds=game->GetActiveZonesForCurrentMap()["BossArena"][0].zone;
geom2d::rect<int>clampedArena{vi2d(arenaBounds.pos+"boss_spawn_ring_radius"_I),vi2d(arenaBounds.size-"boss_spawn_ring_radius"_I*2)};
exitRing.zone.pos.x=std::clamp(exitRing.zone.pos.x,clampedArena.pos.x-"boss_spawn_ring_radius"_I,clampedArena.pos.x-"boss_spawn_ring_radius"_I+clampedArena.size.x);
exitRing.zone.pos.y=std::clamp(exitRing.zone.pos.y,clampedArena.pos.y-"boss_spawn_ring_radius"_I,clampedArena.pos.y-"boss_spawn_ring_radius"_I+clampedArena.size.y);
game->AddZone("EndZone",exitRing); //Create a 144x144 ring around the dead boss.
game->AddActiveZoneToCurrentMap("EndZone",exitRing); //Create a 144x144 ring around the dead boss.
Audio::SetAudioEvent("BossFanfare");

View File

@ -64,7 +64,7 @@ void Menu::InitializeOverworldMapLevelWindow(){
levelSelectWindow->ADD("Enter Button",MenuComponent)(geom2d::rect<float>{{0,166},{windowSize.x-1,16}},"Enter",[](MenuFuncData data){
game->RestockLoadoutItems();
if(game->MAP_DATA[State_OverworldMap::GetCurrentConnectionPoint().map].skipLoadoutScreen||
if(game->MAP_DATA.count(State_OverworldMap::GetCurrentConnectionPoint().map)&&game->MAP_DATA[State_OverworldMap::GetCurrentConnectionPoint().map].skipLoadoutScreen||
State_OverworldMap::GetCurrentConnectionPoint().type.starts_with("STORY")){
State_OverworldMap::StartLevel();
}else{

View File

@ -1083,21 +1083,21 @@ void Player::Moved(MoveFlag::MoveFlag flags){
spawner.SetTriggered(true);
}
}
const std::unordered_map<std::string,std::vector<ZoneData>>&zoneData=game->GetZones(game->GetCurrentLevel());
const safeunorderedmap<std::string,std::vector<ZoneData>>&zoneData=game->GetZones(game->GetCurrentLevel());
if(zoneData.count("UpperZone")){
for(const ZoneData&upperLevelZone:zoneData.at("UpperZone")){
if(geom2d::overlaps(upperLevelZone.zone,pos)){
upperLevel=true;
}
}
}else if(!game->TestingModeEnabled())ERR(std::format("WARNING! Map {} is missing Upper Zones! THIS SHOULD NOT BE HAPPENING!",game->GetCurrentMapDisplayName()));
}else if(!game->TestingModeEnabled())ERR(std::format("WARNING! Map {} is missing Upper Zones! THIS SHOULD NOT BE HAPPENING!",game->GetCurrentMapStageName()));
if(zoneData.count("LowerZone")){
for(const ZoneData&lowerLevelZone:zoneData.at("LowerZone")){
if(geom2d::overlaps(lowerLevelZone.zone,pos)){
upperLevel=false;
}
}
}else if(!game->TestingModeEnabled())ERR(std::format("WARNING! Map {} is missing Lower Zones! THIS SHOULD NOT BE HAPPENING!",game->GetCurrentMapDisplayName()));
}else if(!game->TestingModeEnabled())ERR(std::format("WARNING! Map {} is missing Lower Zones! THIS SHOULD NOT BE HAPPENING!",game->GetCurrentMapStageName()));
EnvironmentalAudio::UpdateEnvironmentalAudio();
if(!std::isfinite(pos.x)){
@ -1337,11 +1337,11 @@ float Player::GetEndZoneStandTime(){
void Player::CheckEndZoneCollision(){
auto HasZoneData=[&](){
return game->GetZones().count("EndZone");
return game->GetActiveZonesForCurrentMap().count("EndZone");
};
if(IsOutOfCombat()&&HasZoneData()){
for(const ZoneData&zone:game->GetZones().at("EndZone")){
for(const ZoneData&zone:game->GetActiveZonesForCurrentMap()["EndZone"]){
if(zone.isUpper==upperLevel&&geom2d::overlaps(GetPos(),zone.zone)){
endZoneStandTime+=game->GetElapsedTime();
if(endZoneStandTime>="Player.End Zone Wait Time"_F){

View File

@ -89,13 +89,13 @@ void State_MainMenu::Draw(AiL*game){
const ZoneData&State_MainMenu::ChooseRandomFocusArea(){
//std::vector<ZoneData>
if(game->GetZones().count("Focus Area")>0){
const std::vector<ZoneData>&zones=game->GetZones().at("Focus Area");
if(game->GetActiveZonesForCurrentMap().count("Focus Area")>0){
const std::vector<ZoneData>&zones=game->GetActiveZonesForCurrentMap()["Focus Area"];
newSelectedFocusAreaIndex=util::random()%zones.size();
return zones[newSelectedFocusAreaIndex];
}else ERR("WARNING! No focus areas included in the intro map!");
return game->GetZones().at("Focus Area")[0];
return game->GetActiveZonesForCurrentMap()["Focus Area"][0];
}
void State_MainMenu::SelectAndMoveToNewFocusArea(){

View File

@ -133,14 +133,13 @@ struct Map{
enum class MapType{
DUNGEON,
BOSS,
STORY,
BLACKSMITH,
WORLD_MAP,
HUB
};
private:
MapTag MapData;
std::string name;
std::string stageName;
Renderable*optimizedTile=nullptr;
std::vector<TilesetTag> TilesetData;
std::vector<LayerTag> LayerData;
@ -155,7 +154,7 @@ private:
std::set<std::string>spawns;
std::map<int,SpawnerTag>SpawnerData; //Spawn groups have IDs, mobs associate which spawner they are tied to via this ID.
std::optional<std::queue<MonsterSpawnerID>>spawnControllerIDs;
std::unordered_map<std::string,std::vector<::ZoneData>>ZoneData;
safeunorderedmap<std::string,std::vector<::ZoneData>>ZoneData;
public:
Map();
void _SetMapData(MapTag data);
@ -166,12 +165,12 @@ public:
const std::vector<LayerTag>&GetLayers()const;
const std::vector<EnvironmentalAudio>&GetEnvironmentalAudio()const;
const float GetDevCompletionTime(Class cl)const;
const MapName&GetMapName()const;
const std::string_view GetMapDisplayName()const;
const MapName&GetMapKeyName()const;
const std::string&GetMapStageName()const;
const bool HasMoreSpawns()const; //Returns whether or not there are more spawns for the spawn controller.
const int Spawn_pop(); //Grabs the next spawn controller ID and removes it from the stack.
const Renderable*const GetOptimizedMap()const;
const std::unordered_map<std::string,std::vector<::ZoneData>>&GetZones()const;
const safeunorderedmap<std::string,std::vector<::ZoneData>>&GetZones()const;
const std::string&GetDefaultAudioEvent()const;
std::string FormatLayerData(std::ostream& os, std::vector<LayerTag>tiles);
std::string FormatSpawnerData(std::ostream& os, std::map<int,SpawnerTag>tiles);
@ -281,8 +280,15 @@ class TMXParser{
}
}
Map::Map(){
ZoneData["UpperZone"];
//Initialize these so that they are valid entries for zones.
ZoneData["LowerZone"];
ZoneData["UpperZone"];
ZoneData["EndZone"];
ZoneData["BossArena"];
ZoneData["Focus Area"];
ZoneData["TrialClock"];
ZoneData["LowerBridgeCollision"];
ZoneData["Hiding Spot"];
}
MapTag::MapTag(){}
MapTag::MapTag(int width,int height,int tilewidth,int tileheight)
@ -323,7 +329,7 @@ class TMXParser{
const Map::MapType&Map::GetMapType()const{
return mapType;
}
const MapName&Map::GetMapName()const{
const MapName&Map::GetMapKeyName()const{
return name;
}
void Map::_SetMapData(MapTag data){
@ -335,7 +341,7 @@ class TMXParser{
const std::vector<EnvironmentalAudio>&Map::GetEnvironmentalAudio()const{
return environmentalAudioData;
}
const std::unordered_map<std::string,std::vector<::ZoneData>>&Map::GetZones()const{
const safeunorderedmap<std::string,std::vector<::ZoneData>>&Map::GetZones()const{
return ZoneData;
}
const std::vector<ItemMapData>&Map::GetStageLoot()const{
@ -344,8 +350,8 @@ class TMXParser{
const std::vector<LayerTag>&Map::GetLayers()const{
return LayerData;
}
const std::string_view Map::GetMapDisplayName()const{
return name;
const std::string&Map::GetMapStageName()const{
return stageName;
}
const bool Map::HasMoreSpawns()const{
return spawnControllerIDs.has_value()&&spawnControllerIDs.value().size()>0;
@ -626,7 +632,7 @@ class TMXParser{
}else
if (newTag.tag=="object"){
if(newTag.data["type"]=="")newTag.data["type"]=newTag.data["name"]; //Found a blank type name! Try to set the type as its name.
std::vector<ZoneData>&zones=parsedMapInfo.ZoneData[newTag.data["type"]];
std::vector<ZoneData>&zones=parsedMapInfo.ZoneData.at(newTag.data["type"]);
float width=1.f;
float height=1.f;
if(newTag.data.count("width")>0)width=newTag.GetFloat("width");
@ -648,11 +654,6 @@ class TMXParser{
std::string accumulator="";
//Initialize these so that they are valid entries for zones.
parsedMapInfo.ZoneData["LowerZone"];
parsedMapInfo.ZoneData["UpperZone"];
parsedMapInfo.ZoneData["EndZone"];
parsedMapInfo.ZoneData["BossArena"];
while (f.good()&&!infiniteMap) {
std::string data;
@ -705,16 +706,16 @@ class TMXParser{
parsedMapInfo.SpawnerData[monster.GetInteger("spawnerLink")].monsters.push_back(monster);
}
parsedMapInfo.ZoneData.SetInitialized();
for(auto&spawnerData:parsedMapInfo.SpawnerData){
SpawnerTag&spawner=spawnerData.second;
for(auto&zoneData:parsedMapInfo.ZoneData){
if(zoneData.first=="UpperZone"){
std::vector<ZoneData>&zones=zoneData.second;
for(ZoneData&zone:zones){
if(geom2d::overlaps(zone.zone,geom2d::rect<int>{{spawner.ObjectData.GetInteger("x"),spawner.ObjectData.GetInteger("y")},{spawner.ObjectData.GetInteger("width"),spawner.ObjectData.GetInteger("height")}})){
spawner.upperLevel=true;
goto continueSpawnerLoop;
}
if(parsedMapInfo.ZoneData.count("UpperZone")){
std::vector<ZoneData>&zones=parsedMapInfo.ZoneData["UpperZone"];
for(ZoneData&zone:zones){
if(geom2d::overlaps(zone.zone,geom2d::rect<int>{{spawner.ObjectData.GetInteger("x"),spawner.ObjectData.GetInteger("y")},{spawner.ObjectData.GetInteger("width"),spawner.ObjectData.GetInteger("height")}})){
spawner.upperLevel=true;
goto continueSpawnerLoop;
}
}
}
@ -724,6 +725,7 @@ class TMXParser{
std::sort(parsedMapInfo.TilesetData.begin(),parsedMapInfo.TilesetData.end(),[](XMLTag&t1,XMLTag&t2){return t1.GetInteger("firstgid")<t2.GetInteger("firstgid");});
std::map<int,int>idToIndexMap; //Since the original map data relies on IDs decided by Tiled and we are condensing all this data into a vector of connection points, each connection point is going to be in a different ID.
//therefore, we need to convert the Tiled IDs into whatever vector index we insert each connection into for State_OverworldMap::connections.
for(auto key:stagePlates){

View File

@ -23,6 +23,7 @@ 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.
DEMO

View File

@ -56,14 +56,15 @@ void Test::RunMapTests(){
is("There are two LowerBridgeCollision zones in Campaign I-I",
game->GetZones("CAMPAIGN_1_1").count("LowerBridgeCollision")
&&game->GetZones("CAMPAIGN_1_1").at("LowerBridgeCollision").size()>=2);
for(auto&map:game->MAP_DATA){
for(auto&[key,size]:DATA["Levels"]){
const Map&map{game->MAP_DATA[key]};
if(map.GetMapType()==Map::MapType::DUNGEON){
is("There is an EndZone in Dungeon "+std::string{map.GetMapDisplayName()},
game->GetZones(map.GetMapName()).count("EndZone"));
is("There is an EndZone in Dungeon "+std::string{map.GetMapStageName()},
game->GetZones(map.GetMapKeyName()).count("EndZone"));
}
for(const ZoneData&endZone:map.GetZones().at("EndZone")){
if(endZone.isUpper&&(!map.GetZones().count("UpperZone")||map.GetZones().at("UpperZone").size()==0))ERR(std::format("WARNING! An End Zone was found in map {} with the upper flag when no upper transition zones have been defined in a stage!",map.GetMapDisplayName()));
for(const ZoneData&endZone:map.GetZones()["EndZone"]){
if(endZone.isUpper&&(!map.GetZones().count("UpperZone")||map.GetZones().at("UpperZone").size()==0))ERR(std::format("WARNING! An End Zone was found in map {} with the upper flag when no upper transition zones have been defined in a stage!",map.GetMapStageName()));
}
}

View File

@ -53,8 +53,6 @@ using A=Attribute;
INCLUDE_game
INCLUDE_DEMO_BUILD
#undef BLACKSMITH
enum class TutorialTaskName{
SET_LOADOUT_ITEM,
MOVE_AROUND,

View File

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

View File

@ -269,7 +269,6 @@
<ellipse/>
</object>
<object id="6" name="Boss Arena" type="BossArena" x="196" y="285" width="1339" height="1395"/>
<object id="7" x="29" y="444"/>
<object id="9" template="../maps/Monsters/Slime King.tx" x="768" y="768">
<properties>
<property name="spawner" type="object" value="3"/>

View File

@ -3,7 +3,7 @@
<properties>
<property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty_loop1"/>
<property name="Level Type" type="int" propertytype="LevelType" value="5"/>
<property name="Level Type" type="int" propertytype="LevelType" value="3"/>
</properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>
<tileset firstgid="2913" source="../maps/No_Shadow_Campfire_24x24.tsx"/>

View File

@ -3,7 +3,7 @@
<properties>
<property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="base_camp"/>
<property name="Level Type" type="int" propertytype="LevelType" value="5"/>
<property name="Level Type" type="int" propertytype="LevelType" value="3"/>
</properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24as12x12.tsx"/>
<tileset firstgid="11649" source="../maps/16x16px_grid_Props_No_Shadow_12x12.tsx"/>

View File

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="152" height="114" tilewidth="24" tileheight="24" infinite="0" nextlayerid="25" nextobjectid="72">
<properties>
<property name="Backdrop" propertytype="Backdrop" value="None"/>
<property name="Level Type" type="int" propertytype="LevelType" value="4"/>
<property name="Level Type" type="int" propertytype="LevelType" value="2"/>
</properties>
<tileset firstgid="1" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>
<tileset firstgid="2913" source="../maps/Decorations_c1_No_Shadow24x24.tsx"/>

View File

@ -2,7 +2,7 @@
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="left-down" width="266" height="184" tilewidth="4" tileheight="4" infinite="0" nextlayerid="5" nextobjectid="66">
<properties>
<property name="Background Music" propertytype="BGM" value="overworld"/>
<property name="Level Type" type="int" propertytype="LevelType" value="4"/>
<property name="Level Type" type="int" propertytype="LevelType" value="2"/>
<property name="Optimize" type="bool" value="true"/>
</properties>
<tileset firstgid="1" source="../maps/Minifantasy_TinyOverworldAllTiles.tsx"/>

View File

@ -1,8 +1,8 @@
INTRO_MAP = 2026-02-23 21:02:53.4861099
HUB = 2024-09-13 21:41:17.0093533
INTRO_MAP = 2026-02-25 23:20:16.1394483
HUB = 2026-02-25 23:20:30.2917000
TEST_MAP = 2026-01-26 20:40:12.7201277
BOSS_1 = 2026-02-23 20:43:43.6912540
WORLD_MAP = 2026-01-26 20:47:50.3754255
BOSS_1 = 2026-02-25 22:36:51.8834583
WORLD_MAP = 2026-02-25 23:20:16.1384937
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

View File

@ -41,6 +41,7 @@ All rights reserved.
#include <map>
#include <unordered_map>
#include<algorithm>
#include<ranges>
//A class that has an initialization lock so that when the lock is activated, any further gets that are missing items in it will report themselves for easier debugging detection.
template<typename T,typename O>
@ -72,7 +73,7 @@ public:
auto insert(T key,O obj){
return map.insert({key,obj});
}
size_t count(T key){
size_t count(T key)const{
return map.count(key);
}
void SetInitialized(){
@ -137,18 +138,28 @@ public:
return items[map[key]];
}
}
const O&operator[](T key)const{
if(initialized&&map.count(key)==0){
ERR("WARNING! Trying to get non-existent key "<<key<<"!")
}
return items.at(map.at(key));
}
O&at(T key){
return items.at(map.at(key));
}
const O&at(T key)const{
return items.at(map.at(key));
}
size_t count(T key){
size_t count(T key)const{
return map.count(key);
}
void SetInitialized(){
initialized=true;
}
//Unlocks the map so items can be added to it again. USE WITH CAUTION! And make sure to lock the map again.
void Unlock(){
initialized=false;
}
size_t size(){
return map.size();
}