diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index 5acab881..d8ac2e94 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -279,7 +279,7 @@ bool AiL::OnUserCreate(){ Inventory::AddItem("Minor Health Potion"s,3); Inventory::AddItem("Bandages"s,10); - LoadLevel("starting_map"_S); + LoadLevel("starting_map"_S,NO_MUSIC_CHANGE); ChangePlayerClass(WARRIOR); GameState::Initialize(); @@ -1821,295 +1821,353 @@ void AiL::InitializeLevel(std::string mapFile,MapName map){ } } -void AiL::LoadLevel(MapName map){ +void AiL::LoadLevel(MapName map,MusicChange changeMusic){ LoadingScreen::loading=true; - #pragma region Reset all data (Loading phase 1) - if(game->MAP_DATA.count(map)==0)ERR(std::format("WARNING! Could not load map {}! Does not exist! Refer to levels.txt for valid maps.",map)); - if(game->MAP_DATA[map].GetMapType()=="Hub"&&GameState::STATE!=GameState::states[States::GAME_HUB])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[GetCurrentLevel()].environmentalAudioData){ - audio.Deactivate(); - } - #pragma endregion + _PrepareLevel(map,changeMusic); +} - SPAWNER_LIST.clear(); - foregroundTileGroups.clear(); - upperForegroundTileGroups.clear(); - MONSTER_LIST.clear(); - ZONE_LIST.clear(); - ItemDrop::drops.clear(); - GameEvent::events.clear(); - worldColor=WHITE; - worldColorFunc=[&](vi2d pos){return game->worldColor;}; +void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){ + LoadingScreen::Reset(); + previousLevel=currentLevel; currentLevel=map; - levelTime=0; - bossName=""; - encounterDuration=0; - totalDamageDealt=0; - encounterStarted=false; - totalBossEncounterMobs=0; - Inventory::Clear("Monster Loot"); - Inventory::Clear("Stage Loot"); - - GetPlayer()->hp=GetPlayer()->GetMaxHealth(); - GetPlayer()->mana=GetPlayer()->GetMaxMana(); - GetPlayer()->SetState(State::NORMAL); - GetPlayer()->GetCastInfo()={}; - GetPlayer()->ResetAccumulatedXP(); + #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()=="Hub"&&GameState::STATE!=GameState::states[States::GAME_HUB])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){ + audio.Deactivate(); + } + #pragma endregion + SPAWNER_LIST.clear(); + foregroundTileGroups.clear(); + upperForegroundTileGroups.clear(); + MONSTER_LIST.clear(); + ZONE_LIST.clear(); + ItemDrop::drops.clear(); + GameEvent::events.clear(); + worldColor=WHITE; + worldColorFunc=[&](vi2d pos){return game->worldColor;}; + levelTime=0; + bossName=""; + encounterDuration=0; + totalDamageDealt=0; + encounterStarted=false; + totalBossEncounterMobs=0; + Inventory::Clear("Monster Loot"); + Inventory::Clear("Stage Loot"); + + GetPlayer()->hp=GetPlayer()->GetMaxHealth(); + GetPlayer()->mana=GetPlayer()->GetMaxMana(); + GetPlayer()->SetState(State::NORMAL); + GetPlayer()->GetCastInfo()={}; + GetPlayer()->ResetAccumulatedXP(); + + ZONE_LIST=game->MAP_DATA[game->GetCurrentLevel()].ZoneData; + return true; + }); #pragma endregion - - ZONE_LIST=game->MAP_DATA[game->GetCurrentLevel()].ZoneData; #pragma region Monster Spawn Data Setup (Loading phase 2) - for(auto&[key,value]:MAP_DATA[map].SpawnerData){ - SpawnerTag&spawnData=MAP_DATA[map].SpawnerData[key]; - std::vector>monster_list; + LoadingScreen::AddPhase([&](){ + for(auto&[key,value]:MAP_DATA[GetCurrentLevel()].SpawnerData){ + SpawnerTag&spawnData=MAP_DATA[GetCurrentLevel()].SpawnerData[key]; + std::vector>monster_list; - vf2d spawnerRadius=vf2d{spawnData.ObjectData.GetFloat("width"),spawnData.ObjectData.GetFloat("height")}/2; - for(XMLTag&monster:spawnData.monsters){ - std::string monsterName=monster.GetString("value"); - monster_list.push_back({monsterName,{monster.GetInteger("x")-spawnData.ObjectData.GetFloat("x"),monster.GetInteger("y")-spawnData.ObjectData.GetFloat("y")}}); + vf2d spawnerRadius=vf2d{spawnData.ObjectData.GetFloat("width"),spawnData.ObjectData.GetFloat("height")}/2; + for(XMLTag&monster:spawnData.monsters){ + std::string monsterName=monster.GetString("value"); + monster_list.push_back({monsterName,{monster.GetInteger("x")-spawnData.ObjectData.GetFloat("x"),monster.GetInteger("y")-spawnData.ObjectData.GetFloat("y")}}); + } + SPAWNER_LIST.push_back(MonsterSpawner{{spawnData.ObjectData.GetFloat("x"),spawnData.ObjectData.GetFloat("y")},spawnerRadius*2,monster_list,spawnData.upperLevel,spawnData.bossNameDisplay}); } - SPAWNER_LIST.push_back(MonsterSpawner{{spawnData.ObjectData.GetFloat("x"),spawnData.ObjectData.GetFloat("y")},spawnerRadius*2,monster_list,spawnData.upperLevel,spawnData.bossNameDisplay}); - } + return true; + }); #pragma endregion #pragma region Identify Upper Foreground Tiles (Loading phase 3) - auto GetUpperZones=[&](){ - for(auto&zoneSet:MAP_DATA[map].ZoneData){ - if(zoneSet.first=="UpperZone"){ //We are interested in all upper zones. - return zoneSet.second; + 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{}; - }; - for(ZoneData&zone:GetUpperZones()){ - int zoneX=zone.zone.pos.x/game->GetCurrentMapData().tilewidth; //snap to grid - int zoneY=zone.zone.pos.y/game->GetCurrentMapData().tilewidth; - int zoneW=zone.zone.right().start.x/game->GetCurrentMapData().tilewidth-zoneX; - int zoneH=zone.zone.bottom().start.y/game->GetCurrentMapData().tilewidth-zoneY; - for(int x=zoneX;x{}; + }; + for(ZoneData&zone:GetUpperZones()){ + int zoneX=zone.zone.pos.x/game->GetCurrentMapData().tilewidth; //snap to grid + int zoneY=zone.zone.pos.y/game->GetCurrentMapData().tilewidth; + int zoneW=zone.zone.right().start.x/game->GetCurrentMapData().tilewidth-zoneX; + int zoneH=zone.zone.bottom().start.y/game->GetCurrentMapData().tilewidth-zoneY; + for(int x=zoneX;xforegroundTilesAdded,upperForegroundTilesAdded; - for(int x=0;xtileset->Sprite()->width/tileSheet.tileset->tilewidth; - int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight; - int tileSheetIndex=tileID-(tileSheet.firstgid-1); - int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1); - int tileSheetX=realTileSheetIndex%tileSheetWidth; - int tileSheetY=realTileSheetIndex/tileSheetWidth; - int checkTileIndex=tileID; - int checkTileID=tileSheetIndex; - #pragma region TileGroupShenanigans - auto SetupTileGroups=[&](std::functionIsForeground,TileRenderData tile,std::set&foregroundTilesIncluded,std::vector&groups){ - if(foregroundTilesIncluded.find({x,y})==foregroundTilesIncluded.end()&&IsForeground(tileSheet,tileSheetIndex)){ - std::queuetileGroupChecks; - TileGroup group; - foregroundTilesIncluded.insert({x,y}); - group.InsertTile(tile); - if(x>0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{-1,0})==foregroundTilesIncluded.end())tileGroupChecks.push({x-1,y}); - if(x0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{0,-1})==foregroundTilesIncluded.end())tileGroupChecks.push({x,y-1}); - if(ytileset->Sprite()->width/tileSheet.tileset->tilewidth; - int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight; - int tileSheetIndex=tileID-(tileSheet.firstgid-1); - int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1); - int tileSheetX=realTileSheetIndex%tileSheetWidth; - int tileSheetY=realTileSheetIndex/tileSheetWidth; - TileRenderData tile={tileSheet,vi2d{pos.x,pos.y}*game->GetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layer2ID}; - if(IsForeground(tileSheet,tileSheetIndex)){ - foregroundTilesIncluded.insert({pos.x,pos.y}); - group.InsertTile(tile); - hadForeground=true; + LoadingScreen::AddPhase([&](){ + std::setforegroundTilesAdded,upperForegroundTilesAdded; + for(int x=0;xtileset->Sprite()->width/tileSheet.tileset->tilewidth; + int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight; + int tileSheetIndex=tileID-(tileSheet.firstgid-1); + int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1); + int tileSheetX=realTileSheetIndex%tileSheetWidth; + int tileSheetY=realTileSheetIndex/tileSheetWidth; + int checkTileIndex=tileID; + int checkTileID=tileSheetIndex; + #pragma region TileGroupShenanigans + auto SetupTileGroups=[&](std::functionIsForeground,TileRenderData tile,std::set&foregroundTilesIncluded,std::vector&groups){ + if(foregroundTilesIncluded.find({x,y})==foregroundTilesIncluded.end()&&IsForeground(tileSheet,tileSheetIndex)){ + std::queuetileGroupChecks; + TileGroup group; + foregroundTilesIncluded.insert({x,y}); + group.InsertTile(tile); + if(x>0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{-1,0})==foregroundTilesIncluded.end())tileGroupChecks.push({x-1,y}); + if(x0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{0,-1})==foregroundTilesIncluded.end())tileGroupChecks.push({x,y-1}); + if(ytileset->Sprite()->width/tileSheet.tileset->tilewidth; + int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight; + int tileSheetIndex=tileID-(tileSheet.firstgid-1); + int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1); + int tileSheetX=realTileSheetIndex%tileSheetWidth; + int tileSheetY=realTileSheetIndex/tileSheetWidth; + TileRenderData tile={tileSheet,vi2d{pos.x,pos.y}*game->GetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layer2ID}; + if(IsForeground(tileSheet,tileSheetIndex)){ + foregroundTilesIncluded.insert({pos.x,pos.y}); + group.InsertTile(tile); + hadForeground=true; + } + layer2ID++; } - layer2ID++; - } - return hadForeground; - }; - IterateThroughOtherLayers({x,y}); - while(!tileGroupChecks.empty()){ - vi2d&pos=tileGroupChecks.front(); - if(IterateThroughOtherLayers(pos,true)){ - foregroundTilesIncluded.insert({pos.x,pos.y}); //Regardless of if we found a foreground tile or not, we need to add this to not get stuck in an infinite loop. - vi2d targetPos=pos+vi2d{-1,0}; - if(pos.x>0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);} - targetPos=pos+vi2d{1,0}; - if(pos.x0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);} - targetPos=pos+vi2d{0,1}; - if(pos.y0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);} + targetPos=pos+vi2d{1,0}; + if(pos.x0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);} + targetPos=pos+vi2d{0,1}; + if(pos.yGetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layerID}; - SetupTileGroups([&](TilesheetData sheet,int tileID){return IsForegroundTile(sheet,tileID);},tile,foregroundTilesAdded,foregroundTileGroups); - SetupTileGroups([&](TilesheetData sheet,int tileID){return IsUpperForegroundTile(tileID);},tile,upperForegroundTilesAdded,upperForegroundTileGroups); - #pragma endregion + }; + TileRenderData tile={tileSheet,vi2d{x,y}*game->GetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layerID}; + SetupTileGroups([&](TilesheetData sheet,int tileID){return IsForegroundTile(sheet,tileID);},tile,foregroundTilesAdded,foregroundTileGroups); + SetupTileGroups([&](TilesheetData sheet,int tileID){return IsUpperForegroundTile(tileID);},tile,upperForegroundTilesAdded,upperForegroundTileGroups); + #pragma endregion + } } + layerID++; } - layerID++; } } - } - for(TileGroup&group:foregroundTileGroups){ - std::sort(group.GetTiles().begin(),group.GetTiles().end(),[](TileRenderData&t1,TileRenderData&t2){return t1.layerID&group){ - std::multimapdata; - using TileDataGroup=std::multimap; //See below. - std::vectorsplitUpData; //This stores every tile group with tiles as a multi map. - std::setiteratedTiles; - for(TileGroup&group:group){ - for(TileRenderData&tile:group.GetTiles()){ - data.insert({tile.pos,tile}); + LoadingScreen::AddPhase([&](){ + auto SplitUp=[&](std::vector&group){ + std::multimapdata; + using TileDataGroup=std::multimap; //See below. + std::vectorsplitUpData; //This stores every tile group with tiles as a multi map. + std::setiteratedTiles; + for(TileGroup&group:group){ + for(TileRenderData&tile:group.GetTiles()){ + data.insert({tile.pos,tile}); + } } - } - auto IsAdjacent=[](int tile1,int tile2,int tileSheetWidth){ - return abs(tile1-tile2)==1||abs(tile1-tile2)>=tileSheetWidth-1&&abs(tile1-tile2)<=tileSheetWidth+1; - }; - for(auto&[key,tile]:data){ - if(iteratedTiles.count(key))continue; - vi2d loc=key; - auto loc_tiles=data.equal_range(loc); - for(auto&it=loc_tiles.first;it!=loc_tiles.second;++it){ //For each tile that exists at this position... - TileRenderData&tile=(*it).second; - bool groupFound=false; - for(TileDataGroup&group:splitUpData){ //See if this position can fit into any existing tile groups - for(int y=-game->GetCurrentMapData().tileheight;y<=game->GetCurrentMapData().tileheight;y+=game->GetCurrentMapData().tileheight){ - for(int x=-game->GetCurrentMapData().tilewidth;x<=game->GetCurrentMapData().tilewidth;x+=game->GetCurrentMapData().tilewidth){ - if(x!=0||y!=0){ //Check every adjacent location for a possible adjacent tile. - vi2d checkOffset=loc+vi2d{x,y}; - auto find_tiles=group.equal_range(checkOffset);//Each tile group consists of tiles that may be adjacent to us. Find all tiles that are adjacent to us in this tile group. - for(auto&it=find_tiles.first;it!=find_tiles.second;++it){ - //These are all tiles that were found adjacent to the location we are checking for. See if they match a potential group. - TileRenderData&foundTile=(*it).second; - if(tile.tileSheet.tileset==foundTile.tileSheet.tileset){ //Let's first see if they are even in the same tileset. - //Let's get how many tiles wide this tile sheet is. - int tileWidth=tile.tileSheet.tileset->tilewidth; - int tileSheetWidth=tile.tileSheet.tileset->tileset->Sprite()->width/tileWidth; - if(IsAdjacent(tile.tileID,foundTile.tileID,tileSheetWidth)){ - group.insert({loc,tile});//We add this tile to the group! It is adjacent! - groupFound=true; - goto groupIterationDone; + auto IsAdjacent=[](int tile1,int tile2,int tileSheetWidth){ + return abs(tile1-tile2)==1||abs(tile1-tile2)>=tileSheetWidth-1&&abs(tile1-tile2)<=tileSheetWidth+1; + }; + for(auto&[key,tile]:data){ + if(iteratedTiles.count(key))continue; + vi2d loc=key; + auto loc_tiles=data.equal_range(loc); + for(auto&it=loc_tiles.first;it!=loc_tiles.second;++it){ //For each tile that exists at this position... + TileRenderData&tile=(*it).second; + bool groupFound=false; + for(TileDataGroup&group:splitUpData){ //See if this position can fit into any existing tile groups + for(int y=-game->GetCurrentMapData().tileheight;y<=game->GetCurrentMapData().tileheight;y+=game->GetCurrentMapData().tileheight){ + for(int x=-game->GetCurrentMapData().tilewidth;x<=game->GetCurrentMapData().tilewidth;x+=game->GetCurrentMapData().tilewidth){ + if(x!=0||y!=0){ //Check every adjacent location for a possible adjacent tile. + vi2d checkOffset=loc+vi2d{x,y}; + auto find_tiles=group.equal_range(checkOffset);//Each tile group consists of tiles that may be adjacent to us. Find all tiles that are adjacent to us in this tile group. + for(auto&it=find_tiles.first;it!=find_tiles.second;++it){ + //These are all tiles that were found adjacent to the location we are checking for. See if they match a potential group. + TileRenderData&foundTile=(*it).second; + if(tile.tileSheet.tileset==foundTile.tileSheet.tileset){ //Let's first see if they are even in the same tileset. + //Let's get how many tiles wide this tile sheet is. + int tileWidth=tile.tileSheet.tileset->tilewidth; + int tileSheetWidth=tile.tileSheet.tileset->tileset->Sprite()->width/tileWidth; + if(IsAdjacent(tile.tileID,foundTile.tileID,tileSheetWidth)){ + group.insert({loc,tile});//We add this tile to the group! It is adjacent! + groupFound=true; + goto groupIterationDone; + } } } } } } } + groupIterationDone: + if(!groupFound){ + splitUpData.push_back({}); + splitUpData.back().insert({loc,tile}); //Since we could not find a group to fit in, we had to start a brand new group. + } } - groupIterationDone: - if(!groupFound){ - splitUpData.push_back({}); - splitUpData.back().insert({loc,tile}); //Since we could not find a group to fit in, we had to start a brand new group. - } + iteratedTiles.insert(loc); } - iteratedTiles.insert(loc); - } - group.clear(); - for(auto&split:splitUpData){ - TileGroup newGroup; - for(auto&[key,value]:split){ - newGroup.InsertTile(value); + group.clear(); + for(auto&split:splitUpData){ + TileGroup newGroup; + for(auto&[key,value]:split){ + newGroup.InsertTile(value); + } + group.push_back(newGroup); } - group.push_back(newGroup); - } - }; - SplitUp(foregroundTileGroups); - SplitUp(upperForegroundTileGroups); + }; + SplitUp(foregroundTileGroups); + SplitUp(upperForegroundTileGroups); + return true; + }); #pragma endregion #pragma region Bridge Layer Setup (Loading Phase 6) - bridgeLayerIndex=-1; - for(int counter=0;LayerTag&layer:MAP_DATA[map].LayerData){ - if(IsBridgeLayer(layer)){ - bridgeLayerIndex=counter; + LoadingScreen::AddPhase([&](){ + bridgeLayerIndex=-1; + for(int counter=0;LayerTag&layer:MAP_DATA[GetCurrentLevel()].LayerData){ + if(IsBridgeLayer(layer)){ + bridgeLayerIndex=counter; + } + counter++; } - counter++; - } + return true; + }); #pragma endregion #pragma region Setup NPCs (Loading Phase 7) - for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){ - if(Unlock::IsUnlocked(data.unlockCondition)){ - MONSTER_LIST.push_back(Monster{data.spawnPos,MONSTER_DATA[data.name]}); - MONSTER_LIST.back().iframe_timer=INFINITE; - MONSTER_LIST.back().npcData=data; - } - } + LoadingScreen::AddPhase([&](){ + for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){ + if(Unlock::IsUnlocked(data.unlockCondition)){ + MONSTER_LIST.push_back(Monster{data.spawnPos,MONSTER_DATA[data.name]}); + MONSTER_LIST.back().iframe_timer=INFINITE; + MONSTER_LIST.back().npcData=data; + } + } + return true; + }); #pragma endregion #pragma region Setup Player and Camera (Loading Phase 8) - player->GetAbility1().cooldown=0.f; - player->GetAbility2().cooldown=0.f; - player->GetAbility3().cooldown=0.f; - player->GetAbility4().cooldown=0.f; - player->GetRightClickAbility().cooldown=0.f; - player->useItem1.cooldown=0.f; - player->useItem2.cooldown=0.f; - player->useItem3.cooldown=0.f; + LoadingScreen::AddPhase([&](){ + player->GetAbility1().cooldown=0.f; + player->GetAbility2().cooldown=0.f; + player->GetAbility3().cooldown=0.f; + player->GetAbility4().cooldown=0.f; + player->GetRightClickAbility().cooldown=0.f; + player->useItem1.cooldown=0.f; + player->useItem2.cooldown=0.f; + player->useItem3.cooldown=0.f; - player->upperLevel=false; //Assume player starts on lower level. - player->ForceSetPos(MAP_DATA[map].MapData.playerSpawnLocation); //Normal set pos does one axis and then the other, so this will make sure that we actually end up at the right spot and ignore collision rules. + player->upperLevel=false; //Assume player starts on lower level. + player->ForceSetPos(MAP_DATA[GetCurrentLevel()].MapData.playerSpawnLocation); //Normal set pos does one axis and then the other, so this will make sure that we actually end up at the right spot and ignore collision rules. - vf2d cameraStartPos=player->GetPos()+vf2d(-24*6,0); - camera.MoveCamera(cameraStartPos); + vf2d cameraStartPos=player->GetPos()+vf2d(-24*6,0); + camera.MoveCamera(cameraStartPos); + return true; + }); #pragma endregion #pragma region Setup Pathfinding (Loading Phase 9) - pathfinder.Initialize(); + LoadingScreen::AddPhase([&](){ + pathfinder.Initialize(); + return true; + }); #pragma endregion - Audio::SetAudioEvent("Default Volume"); - game->audioEngine.fullyLoaded=true; //We assume there's no audio to load, so we just set the audio as fully loaded by default. - if(MAP_DATA[map].bgmSongName.length()>0){ - Audio::PlayBGM(MAP_DATA[map].bgmSongName); - DisableFadeIn(true); + if(changeMusic==PLAY_LEVEL_MUSIC){ + #pragma region Audio Preparation (Loading Phase 10) + LoadingScreen::AddPhase([&](){ + Audio::SetAudioEvent("Default Volume"); + game->audioEngine.fullyLoaded=true; //We assume there's no audio to load, so we just set the audio as fully loaded by default. + if(MAP_DATA[GetCurrentLevel()].bgmSongName.length()>0){ + Audio::PrepareBGM(MAP_DATA[GetCurrentLevel()].bgmSongName); + DisableFadeIn(true); + } + return true; + }); + #pragma endregion + + //Until the audio has stopped (by waiting for a set amount of time), we will respect the audio engine's wishes and not proceed. + LoadingScreen::DeferLoad([&](){return audioEngine.playBGMWaitTime==0.f;}); //This is the wait time for the audio engine to finish. + + #pragma region Audio Channel Loading (Count based on Audio::GetPrepareBGMLoopIterations) + for(int i=0;i0||disableFadeIn||paused; + return fadeOutDuration>0||disableFadeIn||paused||LoadingScreen::loading; } void AiL::PauseGame(){ @@ -3351,9 +3409,9 @@ const bool AiL::GameInitialized()const { rcode AiL::LoadResource(Renderable&renderable,std::string_view imgPath,bool filter,bool clamp){ rcode returnCode; if(gamepack.Loaded()){ - returnCode=renderable.Load(std::string(imgPath),&gamepack); + returnCode=renderable.Load(std::string(imgPath),&gamepack,filter,clamp); }else{ - returnCode=renderable.Load(std::string(imgPath)); + returnCode=renderable.Load(std::string(imgPath),nullptr,filter,clamp); if("GENERATE_GAMEPACK"_B){ gamepack.AddFile(std::string(imgPath)); } diff --git a/Adventures in Lestoria/AdventuresInLestoria.h b/Adventures in Lestoria/AdventuresInLestoria.h index d19bea8d..ba1e1e7d 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.h +++ b/Adventures in Lestoria/AdventuresInLestoria.h @@ -72,6 +72,10 @@ class AiL : public olc::PixelGameEngine std::unique_ptrplayer; SplashScreen splash; public: + enum MusicChange{ + NO_MUSIC_CHANGE, + PLAY_LEVEL_MUSIC, + }; Pathfinding pathfinder; static InputGroup KEY_BACK; static InputGroup KEY_CONFIRM; @@ -127,6 +131,7 @@ private: float lastWorldShakeAdjust=0; vf2d worldShakeVel={}; const float WORLD_SHAKE_ADJUST_MAX_TIME=0.4f; + MapName previousLevel="CAMPAIGN_1_1"; MapName currentLevel="CAMPAIGN_1_1"; std::vectorforegroundTileGroups; std::vectorupperForegroundTileGroups; @@ -169,6 +174,7 @@ private: ResourcePack gamepack; void ValidateGameStatus(); + void _PrepareLevel(MapName map,MusicChange changeMusic); #ifndef __EMSCRIPTEN__ ::discord::Result SetupDiscord(); #endif @@ -186,7 +192,7 @@ public: geom2d::rectNO_COLLISION={{0.f,0.f,},{0.f,0.f}}; TileTransformedView view; void InitializeLevel(std::string mapFile,MapName map); - void LoadLevel(MapName map); + void LoadLevel(MapName map,MusicChange changeMusic=PLAY_LEVEL_MUSIC); void HandleUserInput(float fElapsedTime); void UpdateCamera(float fElapsedTime); void UpdateEffects(float fElapsedTime); diff --git a/Adventures in Lestoria/Audio.cpp b/Adventures in Lestoria/Audio.cpp index a988bfa9..0d083ac7 100644 --- a/Adventures in Lestoria/Audio.cpp +++ b/Adventures in Lestoria/Audio.cpp @@ -160,10 +160,14 @@ void Audio::BGM::Load(){ if(!Self().trackLoadStarted){ Self().trackLoadStarted=true; if(Self().BGMIsPlaying()){ - if(Self().GetTrackName()==songFileName)return; //We are already playing the current track. - BGM&bgm=Self().bgm[Self().GetTrackName()]; - if(Self().BGMIsPlaying()){ - bgm.Unload(); + if(Self().GetTrackName()==songFileName){ //We are already playing the current track. + Self().trackLoadComplete=Self().channelPlayingComplete=Self().fullyLoaded=true; + return; + }else{ + BGM&bgm=Self().bgm[Self().GetTrackName()]; + if(Self().BGMIsPlaying()){ + bgm.Unload(); + } } } Self().currentBGM=songFileName; @@ -290,22 +294,22 @@ void Audio::UpdateLoop(){ Self().currentLoopIndex=0; Self().channelPlayingStarted=true; }else{ - int trackID=track.GetChannelIDs()[Self().currentLoopIndex]; float channelVol=track.GetVolume(Self().currentAudioEvent,Self().currentLoopIndex); Self().prevVolumes.push_back(channelVol); Self().targetVolumes.push_back(channelVol); Engine().SetVolume(trackID,channelVol*GetBGMVolume()); - Engine().Play(trackID,Self().playParams.loop); #pragma region Handle threaded loop indexing Self().currentLoopIndex++; if(Self().currentLoopIndex>=track.GetChannelIDs().size()){ Self().channelPlayingComplete=true; + Self().fullyLoaded=true; } #pragma endregion } - }else{ - Self().fullyLoaded=true; + }else + if(!Self().fullyLoaded){ + ERR("Invalid loading state or too many calls to initialize audio loop! The audio is still not fully loaded!"); } } } @@ -316,12 +320,18 @@ void Audio::PlayBGM(const std::string_view sound,const bool loop){ } void Audio::Update(){ - if(Self().playBGMWaitTime>0.f){ + if(Self().fadeToTargetVolumeTime==0.f&&Self().playBGMWaitTime>0.f){ Self().playBGMWaitTime=std::max(Self().playBGMWaitTime-game->GetElapsedTime(),0.f); if(Self().playBGMWaitTime==0.f&&Self().immediatelyLoadAudio){ while(!Self().BGMFullyLoaded()){ UpdateLoop(); //We immediately load the file. In a loading screen setting we would defer UpdateLoop() such that we have extra time to update the screen, UpdateLoop() is divided into many parts of the music loading process. } + + //Start playing the tracks. + Audio::BGM&track=Self().bgm[Self().GetTrackName()]; + for(int trackID:track.GetChannelIDs()){ + Self().Engine().Play(trackID,true); + } } } if(Self().fadeToTargetVolumeTime>0.f){ @@ -363,4 +373,9 @@ float&Audio::GetSFXVolume(){ float Audio::GetMuteMult(){ if(muted)return 0.f; return 1.f; +} + +int Audio::GetPrepareBGMLoopIterations(std::string_view sound){ + BGM&newBgm=Self().bgm[std::string(sound)]; + return newBgm.GetChannels().size()*2+2; //The channels list gets populated by calling newBgm.Load(), which then provides the list of channels that need to be loaded and played. This is why we multiply by 2. Each of the loading phases also consist of an initialization phase, so we add 2 as well. } \ No newline at end of file diff --git a/Adventures in Lestoria/Audio.h b/Adventures in Lestoria/Audio.h index 0422aa78..0f1b5d42 100644 --- a/Adventures in Lestoria/Audio.h +++ b/Adventures in Lestoria/Audio.h @@ -75,6 +75,8 @@ public: static float&GetBGMVolume(); static float&GetSFXVolume(); static float GetMuteMult(); + //This will get a prepared BGM loop iteration count which is useful for loading stages. + static int GetPrepareBGMLoopIterations(std::string_view sound); private: bool trackLoadStarted=false; bool trackLoadComplete=false; diff --git a/Adventures in Lestoria/GameState.h b/Adventures in Lestoria/GameState.h index f90762f3..c3023185 100644 --- a/Adventures in Lestoria/GameState.h +++ b/Adventures in Lestoria/GameState.h @@ -73,4 +73,5 @@ public: virtual void GetAnyMousePress(int32_t mouseButton); virtual void GetAnyMouseRelease(int32_t mouseButton); static void ChangeState(States::State newState,float fadeOutDuration=0); + virtual void OnLevelLoad()=0; }; \ No newline at end of file diff --git a/Adventures in Lestoria/LoadingScreen.cpp b/Adventures in Lestoria/LoadingScreen.cpp index d3191be4..1b3bae4a 100644 --- a/Adventures in Lestoria/LoadingScreen.cpp +++ b/Adventures in Lestoria/LoadingScreen.cpp @@ -38,27 +38,71 @@ All rights reserved. #include "AdventuresInLestoria.h" #include "LoadingScreen.h" +#include "util.h" INCLUDE_game INCLUDE_WINDOW_SIZE +INCLUDE_ANIMATION_DATA bool LoadingScreen::loading=false; -int LoadingScreen::totalProgress=0; int LoadingScreen::currentProgress=0; -float LoadingScreen::waitTime=0.01f; - -void LoadingScreen::DeferLoad(float waitTime){ +int LoadingScreen::totalProgress=0; +std::queue>LoadingScreen::loadingPhases; +bool LoadingScreen::showGhost=false; +bool LoadingScreen::showLarge=false; +void LoadingScreen::DeferLoad(std::functionwaitCondition){ + AddPhase(waitCondition); } void LoadingScreen::Update(){ if(loading){ + if(loadingPhases.size()>0){ + std::function&loadFunc=loadingPhases.front(); + if(loadFunc()){ + currentProgress++; + loadingPhases.pop(); + } + }else{ + loading=false; + GameState::STATE->OnLevelLoad(); + } } } void LoadingScreen::Draw(){ if(loading){ - game->FillRectDecal({0,0},WINDOW_SIZE,VERY_DARK_GREEN); + game->FillRectDecal({0,0},WINDOW_SIZE,{VERY_DARK_GREEN.r,VERY_DARK_GREEN.g,VERY_DARK_GREEN.b,230}); + + game->FillRectDecal({22.f,WINDOW_SIZE.y-46.f},{(float(currentProgress)/totalProgress)*(WINDOW_SIZE.x-48.f)+4.f,28.f},VERY_DARK_GREEN/2); - game->FillRectDecal({24.f,WINDOW_SIZE.y-48.f},{float(currentProgress/totalProgress)*WINDOW_SIZE.x-48,24.f},DARK_YELLOW); + game->FillRectDecal({24.f,WINDOW_SIZE.y-48.f},{(float(currentProgress)/totalProgress)*(WINDOW_SIZE.x-48.f),24.f},DARK_YELLOW); + game->DrawShadowStringPropDecal({24.f,WINDOW_SIZE.y-60.f},"Loading...",{170,210,0},BLACK,{1.f,1.5f}); + + vf2d playerScale=vf2d(game->GetPlayer()->GetSizeMult(),game->GetPlayer()->GetSizeMult()); + float scale=1.f; + if(showLarge){ + scale=2.f; + } + const std::vectorattackBuffs=game->GetPlayer()->GetStatBuffs({"Attack","Attack %"}); + Pixel blendCol=attackBuffs.size()>0?Pixel{255,uint8_t(255*abs(sin(1.4*attackBuffs[0].duration))),uint8_t(255*abs(sin(1.4*attackBuffs[0].duration)))}:WHITE; + if(showGhost){ + blendCol=BLACK; + } + game->GetPlayer()->GetWalkEAnimation(); + Animate2D::FrameSequence&playerWalkE=ANIMATION_DATA[game->GetPlayer()->GetWalkEAnimation()]; + game->DrawPartialRotatedDecal({(float(currentProgress)/totalProgress)*(WINDOW_SIZE.x-48.f),WINDOW_SIZE.y-36.f},playerWalkE.GetFrame(game->GetRuntime()).GetSourceImage()->Decal(),game->GetPlayer()->GetSpinAngle(),{12,12},playerWalkE.GetFrame(game->GetRuntime()).GetSourceRect().pos,playerWalkE.GetFrame(game->GetRuntime()).GetSourceRect().size,playerScale*scale,blendCol); } +} + +void LoadingScreen::Reset(){ + currentProgress=0; + totalProgress=0; + showGhost=util::random()%6==0; + showLarge=util::random()%6==0; + while(loadingPhases.size()>0)ERR("WARNING! Previous loading phase was not properly cleared! This probably means some part of a previous load did not execute!"); +} + +void LoadingScreen::AddPhase(std::functionloadFunc){ + loadingPhases.push(loadFunc); + totalProgress++; } \ No newline at end of file diff --git a/Adventures in Lestoria/LoadingScreen.h b/Adventures in Lestoria/LoadingScreen.h index 9b7fef6b..7826243b 100644 --- a/Adventures in Lestoria/LoadingScreen.h +++ b/Adventures in Lestoria/LoadingScreen.h @@ -37,13 +37,21 @@ All rights reserved. #pragma endregion #pragma once +#include +#include "olcUTIL_Animate2D.h" + class LoadingScreen{ public: static bool loading; - static int totalProgress; static int currentProgress; + static int totalProgress; static float waitTime; + static bool showGhost; + static bool showLarge; + static std::queue>loadingPhases; static void Update(); static void Draw(); - static void DeferLoad(float waitTime); + static void DeferLoad(std::functionwaitCondition); + static void Reset(); + static void AddPhase(std::functionloadFunc); }; \ No newline at end of file diff --git a/Adventures in Lestoria/State_GameHub.cpp b/Adventures in Lestoria/State_GameHub.cpp index 588fefb6..d86e6e92 100644 --- a/Adventures in Lestoria/State_GameHub.cpp +++ b/Adventures in Lestoria/State_GameHub.cpp @@ -59,6 +59,8 @@ void State_GameHub::OnStateChange(GameState*prevState){ game->GetPlayer()->SetState(State::NORMAL); game->LoadLevel("HUB"); +} +void State_GameHub::OnLevelLoad(){ game->UpdateDiscordStatus("Hub Area",game->GetPlayer()->GetClassName()); } void State_GameHub::OnUserUpdate(AiL*game){ diff --git a/Adventures in Lestoria/State_GameHub.h b/Adventures in Lestoria/State_GameHub.h index 3d70b97b..063d981f 100644 --- a/Adventures in Lestoria/State_GameHub.h +++ b/Adventures in Lestoria/State_GameHub.h @@ -45,4 +45,5 @@ class State_GameHub:public State_GameRun{ virtual void OnStateChange(GameState*prevState)override final; virtual void OnUserUpdate(AiL*game)override final; virtual void Draw(AiL*game)override final; + virtual void OnLevelLoad()override final; }; \ No newline at end of file diff --git a/Adventures in Lestoria/State_GameRun.cpp b/Adventures in Lestoria/State_GameRun.cpp index aab678d7..e7b76023 100644 --- a/Adventures in Lestoria/State_GameRun.cpp +++ b/Adventures in Lestoria/State_GameRun.cpp @@ -73,6 +73,7 @@ void State_GameRun::OnStateChange(GameState*prevState){ game->LoadLevel(State_OverworldMap::GetCurrentConnectionPoint().map); } +void State_GameRun::OnLevelLoad(){} void State_GameRun::OnUserUpdate(AiL*game){ game->bossDisplayTimer=std::max(0.f,game->bossDisplayTimer-game->GetElapsedTime()); if(game->encounterStarted&&game->totalBossEncounterMobs>0){ diff --git a/Adventures in Lestoria/State_GameRun.h b/Adventures in Lestoria/State_GameRun.h index 555eac3d..009408e9 100644 --- a/Adventures in Lestoria/State_GameRun.h +++ b/Adventures in Lestoria/State_GameRun.h @@ -46,6 +46,7 @@ protected: virtual void OnStateChange(GameState*prevState)override; virtual void OnUserUpdate(AiL*game)override; virtual void Draw(AiL*game)override; + virtual void OnLevelLoad()override; void FontTest(); void FontSpriteTest(); }; \ No newline at end of file diff --git a/Adventures in Lestoria/State_LevelComplete.cpp b/Adventures in Lestoria/State_LevelComplete.cpp index e2bc7569..191423ad 100644 --- a/Adventures in Lestoria/State_LevelComplete.cpp +++ b/Adventures in Lestoria/State_LevelComplete.cpp @@ -76,6 +76,7 @@ void State_LevelComplete::OnStateChange(GameState*prevState){ game->GetPlayer()->SetState(State::NORMAL); Menu::OpenMenu(LEVEL_COMPLETE); }; +void State_LevelComplete::OnLevelLoad(){} void State_LevelComplete::OnUserUpdate(AiL*game){ if(levelUpTimer>0.f){ levelUpTimer=std::max(0.f,levelUpTimer-game->GetElapsedTime()); diff --git a/Adventures in Lestoria/State_LevelComplete.h b/Adventures in Lestoria/State_LevelComplete.h index 5fafaf73..0eb6bd21 100644 --- a/Adventures in Lestoria/State_LevelComplete.h +++ b/Adventures in Lestoria/State_LevelComplete.h @@ -48,4 +48,5 @@ class State_LevelComplete:public GameState{ virtual void OnUserUpdate(AiL*game)override final; virtual void Draw(AiL*game)override final; virtual void DrawOverlay(AiL*game)override final; + virtual void OnLevelLoad()override final; }; \ No newline at end of file diff --git a/Adventures in Lestoria/State_MainMenu.cpp b/Adventures in Lestoria/State_MainMenu.cpp index b6d2e7ee..119d0ac6 100644 --- a/Adventures in Lestoria/State_MainMenu.cpp +++ b/Adventures in Lestoria/State_MainMenu.cpp @@ -48,6 +48,7 @@ void State_MainMenu::OnStateChange(GameState*prevState){ TitleScreen::Reset(); game->UpdateDiscordStatus("Main Menu",""); }; +void State_MainMenu::OnLevelLoad(){} void State_MainMenu::OnUserUpdate(AiL*game){ TitleScreen::Update(); if(AiL::KEY_CONFIRM.Released()){ diff --git a/Adventures in Lestoria/State_MainMenu.h b/Adventures in Lestoria/State_MainMenu.h index 9d24b291..4f427621 100644 --- a/Adventures in Lestoria/State_MainMenu.h +++ b/Adventures in Lestoria/State_MainMenu.h @@ -41,4 +41,5 @@ class State_MainMenu:public GameState{ virtual void OnStateChange(GameState*prevState)override final; virtual void OnUserUpdate(AiL*game)override final; virtual void Draw(AiL*game)override final; + virtual void OnLevelLoad()override final; }; \ No newline at end of file diff --git a/Adventures in Lestoria/State_OverworldMap.cpp b/Adventures in Lestoria/State_OverworldMap.cpp index 72966bbd..e3780d6a 100644 --- a/Adventures in Lestoria/State_OverworldMap.cpp +++ b/Adventures in Lestoria/State_OverworldMap.cpp @@ -63,6 +63,8 @@ void State_OverworldMap::OnStateChange(GameState*prevState){ Component(MenuType::PAUSE,"Return to Camp Button")->SetGrayedOut(false); SaveFile::SaveGame(); game->LoadLevel("WORLD_MAP"); +}; +void State_OverworldMap::OnLevelLoad(){ if(Menu::IsMenuOpen()){ Menu::CloseAllMenus(); } @@ -82,7 +84,7 @@ void State_OverworldMap::OnStateChange(GameState*prevState){ } Menu::OpenMenu(OVERWORLD_LEVEL_SELECT,false); game->UpdateDiscordStatus("Overworld Map",game->GetPlayer()->GetClassName()); -}; +} void State_OverworldMap::OnUserUpdate(AiL*game){ if(Menu::stack.size()>1)return; diff --git a/Adventures in Lestoria/State_OverworldMap.h b/Adventures in Lestoria/State_OverworldMap.h index 8cdc00fa..8f394a91 100644 --- a/Adventures in Lestoria/State_OverworldMap.h +++ b/Adventures in Lestoria/State_OverworldMap.h @@ -60,6 +60,7 @@ public: virtual void OnUserUpdate(AiL*game)override final; virtual void Draw(AiL*game)override final; virtual void DrawOverlay(AiL*game)override final; + virtual void OnLevelLoad()override final; static void StartLevel(); static void UpdateCurrentConnectionPoint(const ConnectionPoint&connection); }; \ No newline at end of file diff --git a/Adventures in Lestoria/State_Story.cpp b/Adventures in Lestoria/State_Story.cpp index 3708401a..de20d801 100644 --- a/Adventures in Lestoria/State_Story.cpp +++ b/Adventures in Lestoria/State_Story.cpp @@ -42,6 +42,7 @@ All rights reserved. void State_Story::OnStateChange(GameState*prevState){ Menu::CloseAllMenus(); }; +void State_Story::OnLevelLoad(){} void State_Story::OnUserUpdate(AiL*game){ VisualNovel::novel.Update(); }; diff --git a/Adventures in Lestoria/State_Story.h b/Adventures in Lestoria/State_Story.h index 4da98385..b368922c 100644 --- a/Adventures in Lestoria/State_Story.h +++ b/Adventures in Lestoria/State_Story.h @@ -42,4 +42,5 @@ class State_Story:public GameState{ virtual void OnStateChange(GameState*prevState)override final; virtual void OnUserUpdate(AiL*game)override final; virtual void Draw(AiL*game)override final; + virtual void OnLevelLoad()override final; }; \ No newline at end of file diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 67dc615b..47aa673f 100644 --- a/Adventures in Lestoria/Version.h +++ b/Adventures in Lestoria/Version.h @@ -39,7 +39,7 @@ All rights reserved. #define VERSION_MAJOR 0 #define VERSION_MINOR 3 #define VERSION_PATCH 0 -#define VERSION_BUILD 7526 +#define VERSION_BUILD 7558 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index 21d12dc6..c9962b48 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ