# pragma region License
/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Copyright 2024 Joshua Sigona < sigonasr2 @ gmail . com >
Redistribution and use in source and binary forms , with or without modification ,
are permitted provided that the following conditions are met :
1. Redistributions or derivations of source code must retain the above copyright
notice , this list of conditions and the following disclaimer .
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice . This list of conditions and the following disclaimer must be
reproduced in the documentation and / or other materials provided with the distribution .
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS " AND ANY
EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT ,
INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED
TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR
BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN
CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE .
Portions of this software are copyright © 2024 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# pragma once
# include "olcUTIL_Geometry2D.h"
# include "olcPixelGameEngine.h"
# include <sstream>
# include <set>
# include "EnvironmentalAudio.h"
# include "DEFINES.h"
# include "safemap.h"
# include "ItemMapData.h"
# include "Class.h"
# include "MonsterData.h"
# include <queue>
INCLUDE_MONSTER_DATA
using MapName = std : : string ;
using namespace olc ;
using namespace std : : literals ;
struct XMLTag {
std : : string tag ;
std : : map < std : : string , std : : string > data ;
const std : : string FormatTagData ( std : : map < std : : string , std : : string > tiles ) ;
friend std : : ostream & operator < < ( std : : ostream & os , XMLTag & rhs ) ;
std : : string str ( ) ;
int GetInteger ( std : : string dataTag ) ;
float GetFloat ( std : : string dataTag ) ;
double GetDouble ( std : : string dataTag ) ;
bool GetBool ( std : : string dataTag ) ;
std : : string GetString ( std : : string dataTag ) const ;
} ;
struct ForegroundTileTag : public XMLTag {
//Whether or not to hide this foreground tile
bool hide = true ;
} ;
struct MapTag {
int width = 0 , height = 0 ;
int tilewidth = 0 , tileheight = 0 ;
bool optimized = false ; //An optimized map will require us to flatten it out and use it as a single tile.
bool provideOptimization = false ; //An optimized map will require us to flatten it out and use it as a single tile.
vi2d playerSpawnLocation ;
vi2d MapSize ; //The number of tiles in width and height of this map.
vi2d TileSize ; //How large in pixels the map's tiles are.
MapTag ( ) ;
MapTag ( int width , int height , int tilewidth , int tileheight ) ;
friend std : : ostream & operator < < ( std : : ostream & os , MapTag & rhs ) ;
} ;
struct LayerTag {
XMLTag tag ;
std : : vector < std : : vector < int > > tiles ;
std : : string unlockCondition = " " ;
std : : string str ( ) ;
} ;
struct SpawnerTag {
XMLTag ObjectData ;
std : : vector < XMLTag > monsters ;
bool upperLevel = false ;
std : : string bossNameDisplay ;
std : : string str ( ) ;
friend std : : ostream & operator < < ( std : : ostream & os , SpawnerTag & rhs ) ;
} ;
struct ZoneData {
geom2d : : rect < int > zone ;
bool isUpper = false ;
std : : vector < XMLTag > properties ;
} ;
struct NPCData {
std : : string function = " " ;
std : : string name = " " ;
std : : string sprite = " " ;
std : : string unlockCondition = " " ;
uint32_t roamingRange = 0 ;
vf2d spawnPos ;
NPCData ( ) ;
NPCData ( XMLTag npcTag ) ;
} ;
namespace MonsterTests {
class MonsterTest ;
}
struct Map {
friend class AiL ;
friend class TMXParser ;
private :
MapTag MapData ;
std : : string name ;
Renderable * optimizedTile = nullptr ;
std : : vector < XMLTag > TilesetData ;
std : : vector < LayerTag > LayerData ;
std : : vector < EnvironmentalAudio > environmentalAudioData ;
std : : vector < ItemMapData > stageLoot ;
std : : vector < NPCData > npcs ;
std : : string mapType = " " ;
std : : string bgmSongName = " " ;
std : : unordered_map < Class , float > devCompletionTrialTime ;
BackdropName backdrop = " " ;
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 : : map < std : : string , std : : vector < : : ZoneData > > ZoneData ;
public :
Map ( ) ;
void _SetMapData ( MapTag data ) ;
bool skipLoadoutScreen = false ;
const MapTag & GetMapData ( ) const ;
const std : : string_view GetMapType ( ) const ;
const std : : vector < ItemMapData > & GetStageLoot ( ) const ;
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 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 : : map < std : : string , std : : vector < : : ZoneData > > & GetZones ( ) const ;
std : : string FormatLayerData ( std : : ostream & os , std : : vector < LayerTag > tiles ) ;
std : : string FormatSpawnerData ( std : : ostream & os , std : : map < int , SpawnerTag > tiles ) ;
friend std : : ostream & operator < < ( std : : ostream & os , Map & rhs ) ;
friend std : : ostream & operator < < ( std : : ostream & os , std : : vector < XMLTag > & rhs ) ;
} ;
struct Property {
std : : string name ;
std : : string value ;
int GetInteger ( ) ;
float GetFloat ( ) ;
double GetDouble ( ) ;
bool GetBool ( ) ;
} ;
struct StagePlate {
XMLTag tag ;
std : : map < std : : string , Property > properties ;
} ;
class TMXParser {
public :
Map GetData ( ) ;
private :
Map parsedMapInfo ;
std : : string fileName ;
bool buildingSpawner = false ;
SpawnerTag obj ;
int prevSpawner ;
ZoneData * prevZoneData = nullptr ;
EnvironmentalAudio * prevAudioData = nullptr ;
void ParseTag ( std : : string tag ) ;
int monsterPropertyTagCount = - 1 ;
bool inNPCTag = false ;
XMLTag monsterTag ;
XMLTag npcTag ;
XMLTag spawnerLinkTag ;
StagePlate * currentStagePlate = nullptr ;
std : : optional < XMLTag > spawnControllerTag ;
std : : vector < XMLTag > accumulatedMonsterTags ;
std : : map < int , StagePlate > stagePlates ;
bool infiniteMap = false ;
LayerTag * currentLayerTag = nullptr ;
public :
TMXParser ( std : : string file ) ;
} ;
//#define TMX_PARSER_SETUP //Toggle for code-writing.
# ifdef TMX_PARSER_SETUP
# undef TMX_PARSER_SETUP
# include "State_OverworldMap.h"
extern bool _MAP_LOAD_INFO ;
extern bool _DEBUG_MAP_LOAD_INFO ;
const std : : string XMLTag : : FormatTagData ( std : : map < std : : string , std : : string > tiles ) {
std : : string displayStr = " " ;
for ( std : : map < std : : string , std : : string > : : iterator it = data . begin ( ) ; it ! = data . end ( ) ; it + + ) {
displayStr + = " " + it - > first + " : " + it - > second + " \n " ;
}
return displayStr ;
}
std : : ostream & operator < < ( std : : ostream & os , XMLTag & rhs ) {
os < <
rhs . tag < < " \n " < <
rhs . FormatTagData ( rhs . data ) < < " \n " ;
return os ;
}
std : : ostream & operator < < ( std : : ostream & os , MapTag & rhs ) {
os < <
" (width: " < < rhs . width < < " , height: " < < rhs . height < < " , Tile width: " < < rhs . tilewidth < < " , Tile height: " < < rhs . tileheight < < " ,playerSpawnLocation: " < < rhs . playerSpawnLocation < < " ) \n " ;
return os ;
}
int XMLTag : : GetInteger ( std : : string dataTag ) {
return std : : stoi ( data [ dataTag ] ) ;
}
float XMLTag : : GetFloat ( std : : string dataTag ) {
return std : : stof ( data [ dataTag ] ) ;
}
double XMLTag : : GetDouble ( std : : string dataTag ) {
return std : : stod ( data [ dataTag ] ) ;
}
std : : string XMLTag : : GetString ( std : : string dataTag ) const {
return data . at ( dataTag ) ;
}
bool XMLTag : : GetBool ( std : : string dataTag ) {
if ( data [ dataTag ] = = " 0 " | | data [ dataTag ] = = " false " ) {
return false ;
} else {
return true ;
}
}
int Property : : GetInteger ( ) {
return std : : stoi ( value ) ;
}
float Property : : GetFloat ( ) {
return std : : stof ( value ) ;
}
double Property : : GetDouble ( ) {
return std : : stod ( value ) ;
}
bool Property : : GetBool ( ) {
if ( value = = " 0 " ) {
return false ;
} else {
return true ;
}
}
Map : : Map ( ) {
ZoneData [ " UpperZone " ] ;
ZoneData [ " LowerZone " ] ;
}
MapTag : : MapTag ( ) { }
MapTag : : MapTag ( int width , int height , int tilewidth , int tileheight )
: width ( width ) , height ( height ) , tilewidth ( tilewidth ) , tileheight ( tileheight ) , MapSize ( { width , height } ) , TileSize ( { tilewidth , tileheight } ) { }
std : : string XMLTag : : str ( ) {
return tag + " \n " ;
}
std : : string LayerTag : : str ( ) {
std : : string displayStr = tag . tag + " \n " + tag . FormatTagData ( tag . data ) ;
displayStr + = " DATA ( " + std : : to_string ( tiles [ 0 ] . size ( ) ) + " x " + std : : to_string ( tiles . size ( ) ) + " ) \n " ;
return displayStr ;
}
std : : string SpawnerTag : : str ( ) {
std : : string displayStr = ObjectData . tag + " \n " + ObjectData . FormatTagData ( ObjectData . data ) ;
for ( XMLTag & monster : monsters ) {
displayStr + = " ( " + monster . FormatTagData ( monster . data ) + " ) \n " ;
}
return displayStr ;
}
std : : ostream & operator < < ( std : : ostream & os , SpawnerTag & rhs ) {
os < < rhs . str ( ) < < " \n " ;
return os ;
}
std : : string Map : : FormatLayerData ( std : : ostream & os , std : : vector < LayerTag > tiles ) {
std : : string displayStr ;
for ( int i = 0 ; i < LayerData . size ( ) ; i + + ) {
displayStr + = LayerData [ i ] . str ( ) ;
}
return displayStr ;
}
std : : string Map : : FormatSpawnerData ( std : : ostream & os , std : : map < int , SpawnerTag > tiles ) {
std : : string displayStr ;
for ( auto key : SpawnerData ) {
displayStr + = SpawnerData [ key . first ] . str ( ) ;
}
return displayStr ;
}
const std : : string_view Map : : GetMapType ( ) const {
return mapType ;
}
const MapName & Map : : GetMapName ( ) const {
return name ;
}
void Map : : _SetMapData ( MapTag data ) {
MapData = data ;
}
const MapTag & Map : : GetMapData ( ) const {
return MapData ;
}
const std : : vector < EnvironmentalAudio > & Map : : GetEnvironmentalAudio ( ) const {
return environmentalAudioData ;
}
const std : : map < std : : string , std : : vector < : : ZoneData > > & Map : : GetZones ( ) const {
return ZoneData ;
}
const std : : vector < ItemMapData > & Map : : GetStageLoot ( ) const {
return stageLoot ;
}
const std : : vector < LayerTag > & Map : : GetLayers ( ) const {
return LayerData ;
}
const std : : string_view Map : : GetMapDisplayName ( ) const {
return name ;
}
const bool Map : : HasMoreSpawns ( ) const {
return spawnControllerIDs . has_value ( ) & & spawnControllerIDs . value ( ) . size ( ) > 0 ;
}
const int Map : : Spawn_pop ( ) {
if ( ! HasMoreSpawns ( ) ) ERR ( " WARNING! Trying to pop from queue when there are no items! Make sure to use HasMoreSpawns() first! " ) ;
const int nextSpawnId = spawnControllerIDs . value ( ) . front ( ) ;
spawnControllerIDs . value ( ) . pop ( ) ;
return nextSpawnId ;
}
const Renderable * const Map : : GetOptimizedMap ( ) const {
return optimizedTile ;
}
NPCData : : NPCData ( ) { }
NPCData : : NPCData ( XMLTag npcTag ) {
const std : : array < std : : string , 7 > tags = { " Function " , " NPC Name " , " Roaming Range " , " Unlock Condition " , " Spritesheet " , " x " , " y " } ;
for ( int i = 0 ; i < tags . size ( ) ; i + + ) {
if ( npcTag . data . contains ( tags [ i ] ) ) {
switch ( i ) {
case 0 : { //Function
function = npcTag . GetString ( tags [ i ] ) ;
} break ;
case 1 : { //NPC Name
name = npcTag . GetString ( tags [ i ] ) ;
} break ;
case 2 : { //Roaming Range
roamingRange = npcTag . GetInteger ( tags [ i ] ) ;
} break ;
case 3 : { //Unlock Condition
unlockCondition = npcTag . GetString ( tags [ i ] ) ;
} break ;
case 4 : { //Spritesheet
sprite = npcTag . GetString ( tags [ i ] ) ;
} break ;
case 5 : { //X
spawnPos . x = npcTag . GetFloat ( tags [ i ] ) ;
} break ;
case 6 : { //Y
spawnPos . y = npcTag . GetFloat ( tags [ i ] ) ;
} break ;
}
}
}
}
std : : ostream & operator < < ( std : : ostream & os , std : : vector < XMLTag > & rhs ) {
for ( XMLTag & tag : rhs ) {
os < <
tag < < " \n " ;
}
return os ;
}
std : : ostream & operator < < ( std : : ostream & os , Map & rhs ) {
os < <
rhs . MapData < < " \n " < <
rhs . TilesetData < < " \n " < <
rhs . FormatLayerData ( os , rhs . LayerData ) < < " \n " < <
rhs . FormatSpawnerData ( os , rhs . SpawnerData ) < < " \n " ;
return os ;
}
Map TMXParser : : GetData ( ) {
return parsedMapInfo ;
}
void TMXParser : : ParseTag ( std : : string tag ) {
auto ReadNextTag = [ & ] ( ) {
XMLTag newTag ;
//First character is a '<' so we discard it.
tag . erase ( 0 , 1 ) ; tag . erase ( tag . length ( ) - 1 , 1 ) ; //Erase the first and last characters in the tag. Now parse by spaces.
std : : stringstream s ( tag ) ; //Turn it into a string stream to now parse into individual whitespaces.
std : : string data ;
while ( s . good ( ) ) {
int quotationMarkCount = 0 ;
bool pastEquals = false ;
data = " " ;
bool valid = false ;
while ( s . good ( ) ) {
int character = s . get ( ) ;
if ( character = = ' " ' ) {
quotationMarkCount + + ;
}
if ( character = = ' ' & & quotationMarkCount % 2 = = 0 ) {
valid = true ;
break ;
}
data + = character ;
if ( pastEquals & & quotationMarkCount % 2 = = 0 ) {
valid = true ;
break ;
}
if ( character = = ' = ' & & quotationMarkCount % 2 = = 0 ) {
pastEquals = true ;
}
}
if ( valid & & data . length ( ) > 0 ) {
if ( newTag . tag . length ( ) = = 0 ) { //Tag's empty, so first line is the tag.
newTag . tag = data ;
# if _DEBUG
if ( _DEBUG_MAP_LOAD_INFO ) LOG ( " Tag: " < < newTag . tag < < " \n " ) ;
# endif
} else {
std : : string key = data . substr ( 0 , data . find ( " = " ) ) ;
std : : string value = data . substr ( data . find ( " = " ) + 1 , std : : string : : npos ) ;
//Strip Quotation marks.
value = value . substr ( 1 , std : : string : : npos ) ;
value = value . substr ( 0 , value . length ( ) - 1 ) ;
newTag . data [ key ] = value ;
# if _DEBUG
if ( _DEBUG_MAP_LOAD_INFO ) LOG ( " " < < key < < " : " < < newTag . data [ key ] < < " \n " ) ;
# endif
}
}
}
return newTag ;
} ;
XMLTag newTag = ReadNextTag ( ) ;
auto ParseMonsterTemplateName = [ ] ( const XMLTag & monsterTag ) {
std : : string monsterName = monsterTag . GetString ( " template " ) . substr ( " ../maps/Monsters/ " s . length ( ) ) ;
monsterName = monsterName . substr ( 0 , monsterName . find ( " .tx " ) ) ;
return monsterName ;
} ;
if ( newTag . tag ! = " property " & & newTag . tag ! = " properties " & & monsterPropertyTagCount = = 0 ) {
ERR ( std : : format ( " WARNING! Monster {} at ({},{}) in Map {} does not have a spawn set! " , ParseMonsterTemplateName ( monsterTag ) , monsterTag . GetFloat ( " x " ) , monsterTag . GetFloat ( " y " ) , fileName ) ) ;
}
if ( newTag . tag = = " object " & & newTag . data [ " type " ] ! = " StagePlate " ) {
currentStagePlate = nullptr ;
}
if ( newTag . tag = = " object " & & newTag . data [ " type " ] ! = " NPC " & & inNPCTag ) {
inNPCTag = false ;
parsedMapInfo . npcs . push_back ( NPCData { npcTag } ) ;
}
if ( newTag . tag = = " object " & & newTag . data [ " type " ] ! = " SpawnController " & & spawnControllerTag . has_value ( ) ) spawnControllerTag = { } ;
if ( newTag . tag = = " map " ) {
if ( stoi ( newTag . data [ " infinite " ] ) = = 1 ) {
infiniteMap = true ;
ERR ( std : : format ( " WARNING! Infinite maps are not supported! Invalid map: {} " , fileName ) ) ;
return ;
}
if ( newTag . data . find ( " class " ) = = newTag . data . end ( ) ) ERR ( std : : format ( " WARNING! Map {} does not have a class set! " , fileName ) ) ;
if ( newTag . data [ " class " ] ! = " Map " ) ERR ( std : : format ( " WARNING! Map {} is not set to the map class! Class is set to {} instead. " , fileName , newTag . data [ " class " ] ) ) ;
parsedMapInfo . MapData = { stoi ( newTag . data [ " width " ] ) , stoi ( newTag . data [ " height " ] ) , stoi ( newTag . data [ " tilewidth " ] ) , stoi ( newTag . data [ " tileheight " ] ) } ;
} else
if ( newTag . tag = = " tileset " ) {
parsedMapInfo . TilesetData . push_back ( newTag ) ;
} else
if ( newTag . tag = = " layer " ) {
LayerTag l = { newTag } ;
parsedMapInfo . LayerData . push_back ( l ) ;
currentLayerTag = & parsedMapInfo . LayerData . back ( ) ;
} else
if ( newTag . tag = = " object " & & newTag . data [ " type " ] = = " SpawnController " ) {
spawnControllerTag = newTag ;
} else
if ( newTag . tag = = " object " & & newTag . data [ " type " ] = = " SpawnGroup " ) {
if ( newTag . GetInteger ( " id " ) ! = 0 ) {
parsedMapInfo . SpawnerData [ newTag . GetInteger ( " id " ) ] = { newTag } ;
prevSpawner = newTag . GetInteger ( " id " ) ;
}
} else
if ( newTag . tag = = " property " & & newTag . data [ " name " ] = = " Layer Unlock Condition " & & currentLayerTag ! = nullptr ) {
currentLayerTag - > unlockCondition = newTag . data [ " value " ] ;
} else
if ( newTag . tag = = " property " & & newTag . data [ " name " ] = = " Upper? " & & prevZoneData ! = nullptr ) {
prevZoneData - > isUpper = newTag . GetBool ( " value " ) ;
} else
if ( newTag . tag = = " property " & & newTag . data [ " name " ] = = " Optimize " & & newTag . data [ " value " ] = = " true " ) {
parsedMapInfo . MapData . optimized = true ;
} else
if ( newTag . tag = = " property " & & newTag . data [ " name " ] = = " Create Optimization Map (Override) " & & newTag . data [ " value " ] = = " true " ) {
parsedMapInfo . MapData . provideOptimization = true ;
} else
if ( newTag . tag = = " property " & & newTag . data [ " name " ] = = " Boss Title Display " ) {
parsedMapInfo . SpawnerData [ prevSpawner ] . bossNameDisplay = newTag . data [ " value " ] ;
} else
if ( newTag . tag = = " property " & & newTag . data [ " name " ] = = " Level Type " ) {
parsedMapInfo . mapType = newTag . data [ " value " ] ;
} else
if ( newTag . tag = = " property " & & newTag . data [ " name " ] = = " Background Music " ) {
if ( newTag . data [ " value " ] ! = " None " ) { //None is a default value that we ignore.
parsedMapInfo . bgmSongName = newTag . data [ " value " ] ;
}
} else
if ( newTag . tag = = " property " & & newTag . data [ " name " ] . starts_with ( " Dev Completion Time " ) ) {
if ( newTag . data . count ( " value " ) ) { //None is a default value that we ignore.
size_t classStartPos = " Dev Completion Time - " s . length ( ) ;
std : : string className = newTag . data [ " name " ] . substr ( classStartPos , newTag . data [ " name " ] . find ( ' ' , classStartPos ) - classStartPos ) ;
parsedMapInfo . devCompletionTrialTime [ classutils : : StringToClass ( className ) ] = newTag . GetFloat ( " value " ) ;
}
} else
if ( newTag . tag = = " property " & & newTag . data [ " name " ] = = " Backdrop " ) {
if ( newTag . data [ " value " ] ! = " None " ) { //None is a default value that we ignore.
parsedMapInfo . backdrop = newTag . data [ " value " ] ;
}
} else
if ( newTag . tag = = " object " & & newTag . data [ " type " ] = = " AudioEnvironmentalSound " ) {
parsedMapInfo . environmentalAudioData . emplace_back ( ) ;
prevAudioData = & parsedMapInfo . environmentalAudioData . back ( ) ;
prevAudioData - > SetPos ( { newTag . GetFloat ( " x " ) , newTag . GetFloat ( " y " ) } ) ;
} else
if ( newTag . tag = = " property " & & newTag . data [ " propertytype " ] = = " EnvironmentalSounds " ) {
if ( newTag . data [ " value " ] ! = " None " ) { //None is a default value that we ignore.
prevAudioData - > SetAudioName ( newTag . data [ " value " ] ) ;
}
} else
if ( newTag . tag = = " object " & & newTag . data [ " type " ] = = " PlayerSpawnLocation " ) {
float width = 1.f ;
float height = 1.f ;
if ( newTag . data . count ( " width " ) > 0 ) width = newTag . GetFloat ( " width " ) ;
if ( newTag . data . count ( " height " ) > 0 ) height = newTag . GetFloat ( " height " ) ;
parsedMapInfo . MapData . playerSpawnLocation = { int ( newTag . GetFloat ( " x " ) + width / 2 ) , int ( newTag . GetFloat ( " y " ) + height / 2 ) } ;
} else
if ( newTag . tag = = " object " & & newTag . data [ " type " ] = = " NPC " ) {
if ( inNPCTag ) parsedMapInfo . npcs . push_back ( NPCData { npcTag } ) ;
npcTag = newTag ;
inNPCTag = true ;
} else
if ( newTag . tag = = " object " & & newTag . data [ " template " ] . starts_with ( " ../maps/Monsters " ) ) {
monsterTag = newTag ;
monsterPropertyTagCount = 0 ;
} else
if ( newTag . tag = = " property " & & spawnControllerTag . has_value ( ) ) {
if ( ! parsedMapInfo . HasMoreSpawns ( ) ) parsedMapInfo . spawnControllerIDs = std : : queue < int > { } ;
parsedMapInfo . spawnControllerIDs . value ( ) . push ( newTag . GetInteger ( " value " ) ) ;
} else
if ( newTag . tag = = " property " & & inNPCTag ) {
npcTag . data [ newTag . data [ " name " ] ] = newTag . data [ " value " ] ;
} else
if ( newTag . tag = = " property " & & monsterPropertyTagCount = = 0 ) {
std : : string monsterName = ParseMonsterTemplateName ( monsterTag ) ;
if ( ! MONSTER_DATA . count ( monsterName ) ) ERR ( std : : format ( " WARNING! Could not find monster type {} " , monsterName ) ) ;
parsedMapInfo . spawns . insert ( monsterName ) ;
spawnerLinkTag = newTag ;
monsterTag . data [ " value " ] = monsterName ;
monsterTag . data [ " spawnerLink " ] = spawnerLinkTag . data [ " value " ] ;
accumulatedMonsterTags . push_back ( monsterTag ) ;
monsterPropertyTagCount = - 1 ;
} else
if ( newTag . tag = = " object " & & newTag . data [ " type " ] = = " StagePlate " ) {
if ( newTag . GetInteger ( " id " ) ! = 0 ) {
stagePlates [ newTag . GetInteger ( " id " ) ] = { newTag } ;
currentStagePlate = & stagePlates . at ( newTag . GetInteger ( " id " ) ) ;
}
} else
if ( newTag . tag = = " property " & & currentStagePlate ! = nullptr ) {
currentStagePlate - > properties [ newTag . data [ " name " ] ] = { newTag . data [ " name " ] , newTag . data [ " value " ] } ;
} else
if ( newTag . tag = = " property " & & prevZoneData ! = nullptr ) {
//This is a property for a zone that doesn't fit into the other categories, we add it to the previous zone data encountered.
prevZoneData - > properties . push_back ( newTag ) ;
} else
if ( newTag . tag = = " object " & & newTag . data . find ( " type " ) ! = newTag . data . end ( ) ) {
//This is an object with a type that doesn't fit into other categories, we can add it to ZoneData.
std : : vector < ZoneData > & zones = parsedMapInfo . ZoneData [ newTag . data [ " type " ] ] ;
float width = 1.f ;
float height = 1.f ;
if ( newTag . data . count ( " width " ) > 0 ) width = newTag . GetFloat ( " width " ) ;
if ( newTag . data . count ( " height " ) > 0 ) height = newTag . GetFloat ( " height " ) ;
zones . emplace_back ( geom2d : : rect < int > { { newTag . GetInteger ( " x " ) , newTag . GetInteger ( " y " ) } , { int ( width ) , int ( height ) } } ) ;
prevZoneData = & zones . back ( ) ;
} else {
# ifdef _DEBUG
if ( _DEBUG_MAP_LOAD_INFO ) LOG ( " Unsupported tag format! Ignoring. " < < " \n " ) ;
# endif
}
# ifdef _DEBUG
if ( _DEBUG_MAP_LOAD_INFO ) LOG ( " \n " < < " ============= \n " ) ;
# endif
}
TMXParser : : TMXParser ( std : : string file ) {
fileName = file ;
std : : ifstream f ( file , std : : ios : : in ) ;
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 ;
f > > data ;
if ( data . empty ( ) ) continue ;
if ( accumulator . length ( ) > 0 ) {
accumulator + = " " + data ;
//Check if it ends with '>'
if ( data [ data . length ( ) - 1 ] = = ' > ' ) {
ParseTag ( accumulator ) ;
accumulator = " " ;
}
} else
if ( data [ 0 ] = = ' < ' ) {
//Beginning of XML tag.
accumulator = data ;
if ( accumulator . length ( ) > 1 & & accumulator . at ( 1 ) = = ' / ' ) {
accumulator = " " ; //Restart because this is an end tag.
}
if ( accumulator . length ( ) > 1 & & accumulator . find ( ' > ' ) ! = std : : string : : npos ) {
accumulator = " " ; //Restart because this tag has nothing in it!
}
} else {
//Start reading in data for this layer.
std : : vector < int > rowData ;
while ( data . find ( " , " ) ! = std : : string : : npos ) {
std : : string datapiece = data . substr ( 0 , data . find ( " , " ) ) ;
data = data . substr ( data . find ( " , " ) + 1 , std : : string : : npos ) ;
rowData . push_back ( stoi ( datapiece ) ) ;
}
if ( data . length ( ) ) {
rowData . push_back ( stoi ( data ) ) ;
}
parsedMapInfo . LayerData [ parsedMapInfo . LayerData . size ( ) - 1 ] . tiles . push_back ( rowData ) ;
}
}
if ( inNPCTag ) {
parsedMapInfo . npcs . push_back ( NPCData { npcTag } ) ;
}
if ( infiniteMap ) {
# if _DEBUG
if ( _DEBUG_MAP_LOAD_INFO ) LOG ( " Infinite map detected. Parsing stopped early. " ) ;
# endif
}
for ( XMLTag & monster : accumulatedMonsterTags ) {
parsedMapInfo . SpawnerData [ monster . GetInteger ( " spawnerLink " ) ] . monsters . push_back ( monster ) ;
}
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 ;
}
}
}
}
continueSpawnerLoop :
continue ;
}
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 ) {
StagePlate & plate = key . second ;
idToIndexMap [ plate . tag . GetInteger ( " id " ) ] = int ( State_OverworldMap : : connections . size ( ) ) ;
ConnectionPoint newConnection = { { { plate . tag . GetFloat ( " x " ) , plate . tag . GetFloat ( " y " ) } , { plate . tag . GetFloat ( " width " ) , plate . tag . GetFloat ( " height " ) } } , plate . properties [ " Type " ] . value , plate . tag . data [ " name " ] , plate . properties [ " Map " ] . value , plate . properties [ " Unlock Condition " ] . value , { } } ;
int iterationCount = 0 ;
for ( auto key2 : plate . properties ) {
if ( key2 . first . starts_with ( " Connection " ) ) {
int direction = stoi ( key2 . first . substr ( " Connection " s . length ( ) , 1 ) ) - 1 ;
int & neighborElement = newConnection . neighbors [ direction ] ;
if ( neighborElement ! = - 1 ) ERR ( std : : format ( " WARNING! Connection Point in direction {} is already occupied by node {}! " , direction , neighborElement ) ) ;
newConnection . neighbors [ direction ] = key2 . second . GetInteger ( ) ;
}
}
State_OverworldMap : : connections . push_back ( newConnection ) ;
}
for ( ConnectionPoint & connection : State_OverworldMap : : connections ) {
for ( int & val : connection . neighbors ) {
if ( idToIndexMap . count ( val ) ) {
val = idToIndexMap . at ( val ) ; //Convert from given Tiled ID to indexed ID in State_OverworldMap::connections
}
}
}
for ( int counter = 0 ; ConnectionPoint & connection : State_OverworldMap : : connections ) {
for ( int directionInd = 0 ; int val : connection . neighbors ) {
if ( val ! = - 1 ) {
ConnectionPoint & neighbor = State_OverworldMap : : connections . at ( val ) ;
//Find the opposite slot.
int targetInd = - 1 ;
# pragma region Opposite Direction Calculation
switch ( directionInd ) {
case ConnectionPoint : : NORTH : {
targetInd = ConnectionPoint : : SOUTH ;
} break ;
case ConnectionPoint : : EAST : {
targetInd = ConnectionPoint : : WEST ;
} break ;
case ConnectionPoint : : SOUTH : {
targetInd = ConnectionPoint : : NORTH ;
} break ;
case ConnectionPoint : : WEST : {
targetInd = ConnectionPoint : : EAST ;
} break ;
}
# pragma endregion
if ( neighbor . neighbors [ targetInd ] = = - 1 ) { //We insert our neighbor pairing here.
neighbor . neighbors [ targetInd ] = counter ;
}
}
directionInd + + ;
}
counter + + ;
}
# ifdef _DEBUG
if ( _DEBUG_MAP_LOAD_INFO ) LOG ( " Parsed Map Data: \n " < < parsedMapInfo < < " \n " ) ;
# endif
}
# endif