The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'!
https://forums.lestoria.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
739 lines
30 KiB
739 lines
30 KiB
#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);
|
|
};
|
|
|
|
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;
|
|
const std::map<std::string,std::vector<::ZoneData>>&GetZones()const;
|
|
public:
|
|
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;
|
|
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;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
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==' '&"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
|
|
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"&¤tLayerTag!=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"&¤tStagePlate!=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 |