#pragma once #include "olcPixelGameEngine.h" #include "olcUTIL_Geometry2D.h" #include <strstream> using namespace olc; 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); }; struct MapTag{ int width=0,height=0; vi2d playerSpawnLocation; friend std::ostream& operator << (std::ostream& os, MapTag& rhs); }; struct LayerTag{ XMLTag tag; std::vector<std::vector<int>> tiles; std::string str(); }; struct SpawnerTag{ XMLTag ObjectData; std::vector<XMLTag>monsters; bool upperLevel=false; std::string str(); friend std::ostream& operator << (std::ostream& os, SpawnerTag& rhs); }; struct Map{ MapTag MapData; std::vector<XMLTag> TilesetData; std::vector<LayerTag> LayerData; std::map<int,SpawnerTag> SpawnerData; //Spawn groups have IDs, mobs associate which spawner they are tied to via this ID. std::map<std::string,std::vector<geom2d::rect<int>>> ZoneData; 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); }; class TMXParser{ public: Map GetData(); private: Map parsedMapInfo; bool buildingSpawner=false; SpawnerTag obj; void ParseTag(std::string tag); int monsterPropertyTagCount=-1; XMLTag monsterTag; XMLTag spawnerLinkTag; std::vector<XMLTag>accumulatedMonsterTags; bool infiniteMap=false; public: TMXParser(std::string file); }; typedef std::map<std::string,std::vector<geom2d::rect<int>>> ZoneData; #ifdef TMX_PARSER_SETUP #undef TMX_PARSER_SETUP 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<<",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]); } bool XMLTag::GetBool(std::string dataTag) { if (data[dataTag]=="0") { return false; } else { return true; } } 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; } 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==' '&"ationMarkCount%2==0){ valid=true; break; } data+=character; if(pastEquals&"ationMarkCount%2==0){ valid=true; break; } if(character=='='&"ationMarkCount%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; std::cout<<"Tag: "<<newTag.tag<<"\n"; } 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; std::cout<<" "<<key<<":"<<newTag.data[key]<<"\n"; } } } return newTag; }; XMLTag newTag=ReadNextTag(); if (newTag.tag=="map") { if(stoi(newTag.data["infinite"])==1){ infiniteMap=true; return; } parsedMapInfo.MapData={stoi(newTag.data["width"]),stoi(newTag.data["height"])}; } else if (newTag.tag=="tileset") { parsedMapInfo.TilesetData.push_back(newTag); } else if (newTag.tag=="layer") { LayerTag l = {newTag}; parsedMapInfo.LayerData.push_back(l); }else if (newTag.tag=="object"&&newTag.data["type"]=="SpawnGroup") { parsedMapInfo.SpawnerData[newTag.GetInteger("id")]={newTag}; } else if (newTag.tag=="object"&&newTag.data["type"]=="PlayerSpawnLocation") { parsedMapInfo.MapData.playerSpawnLocation={newTag.GetInteger("x")-newTag.GetInteger("width")/2,newTag.GetInteger("y")-newTag.GetInteger("height")/2}; } else if (newTag.tag=="object"&&newTag.data.find("type")!=newTag.data.end() &&newTag.data.find("x")!=newTag.data.end() &&newTag.data.find("y")!=newTag.data.end() &&newTag.data.find("width")!=newTag.data.end() &&newTag.data.find("height")!=newTag.data.end()){ //This is an object with a type that doesn't fit into other categories, we can add it to ZoneData. if(parsedMapInfo.ZoneData.find(newTag.data["type"])!=parsedMapInfo.ZoneData.end()){ std::vector<geom2d::rect<int>>&zones=parsedMapInfo.ZoneData[newTag.data["type"]]; zones.push_back({{newTag.GetInteger("x"),newTag.GetInteger("y")},{newTag.GetInteger("width"),newTag.GetInteger("height")}}); } else { std::vector<geom2d::rect<int>>zones; zones.push_back({{newTag.GetInteger("x"),newTag.GetInteger("y")},{newTag.GetInteger("width"),newTag.GetInteger("height")}}); parsedMapInfo.ZoneData[newTag.data["type"]]=zones; } }else if (newTag.tag=="object"&&newTag.data["type"]=="Monster") { //XMLTag monsterTag=ReadNextTag(); //XMLTag spawnerLinkTag=ReadNextTag(); //newTag.data["value"]=monsterTag.GetInteger("value"); //Value now contains which monster name this spawn represents. monsterTag=newTag; monsterPropertyTagCount=0; } else if (newTag.tag=="property"&&monsterPropertyTagCount==0) { monsterTag.data["value"]=newTag.data["value"]; monsterPropertyTagCount++; } else if (newTag.tag=="property"&&monsterPropertyTagCount==1) { spawnerLinkTag=newTag; monsterTag.data["spawnerLink"]=spawnerLinkTag.data["value"]; accumulatedMonsterTags.push_back(monsterTag); monsterPropertyTagCount=-1; } else { std::cout<<"Unsupported tag format! Ignoring."<<"\n"; } std::cout<<"\n"<<"=============\n"; } TMXParser::TMXParser(std::string file){ std::ifstream f(file,std::ios::in); std::string accumulator=""; 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(infiniteMap){ std::cout<<"Infinite map detected. Parsing stopped early."<<std::endl; } 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<geom2d::rect<int>>&zones=zoneData.second; for(geom2d::rect<int>&zone:zones){ if(geom2d::overlaps(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::cout<<"Parsed Map Data:\n"<<parsedMapInfo<<"\n"; } #endif