|
|
|
#pragma once
|
|
|
|
#include "olcPixelGameEngine.h"
|
|
|
|
#include "olcUTIL_Geometry2D.h"
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
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;
|
|
|
|
int tilewidth=0,tileheight=0;
|
|
|
|
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 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 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;
|
|
|
|
int prevSpawner;
|
|
|
|
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
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
bool XMLTag::GetBool(std::string dataTag) {
|
|
|
|
if (data[dataTag]=="0") {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
if(_DEBUG_MAP_LOAD_INFO)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;
|
|
|
|
if(_DEBUG_MAP_LOAD_INFO)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"]),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);
|
|
|
|
}else
|
|
|
|
if (newTag.tag=="object"&&newTag.data["type"]=="SpawnGroup") {
|
|
|
|
parsedMapInfo.SpawnerData[newTag.GetInteger("id")]={newTag};
|
|
|
|
prevSpawner=newTag.GetInteger("id");
|
|
|
|
} else
|
|
|
|
if (newTag.tag=="property"&&newTag.data["name"]=="Boss Title Display") {
|
|
|
|
parsedMapInfo.SpawnerData[prevSpawner].bossNameDisplay=newTag.data["value"];
|
|
|
|
} 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 {
|
|
|
|
if(_DEBUG_MAP_LOAD_INFO)std::cout<<"Unsupported tag format! Ignoring."<<"\n";
|
|
|
|
}
|
|
|
|
if(_DEBUG_MAP_LOAD_INFO)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){
|
|
|
|
if(_DEBUG_MAP_LOAD_INFO)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::sort(parsedMapInfo.TilesetData.begin(),parsedMapInfo.TilesetData.end(),[](XMLTag&t1,XMLTag&t2){return t1.GetInteger("firstgid")<t2.GetInteger("firstgid");});
|
|
|
|
|
|
|
|
if(_DEBUG_MAP_LOAD_INFO)std::cout<<"Parsed Map Data:\n"<<parsedMapInfo<<"\n";
|
|
|
|
}
|
|
|
|
#endif
|