diff --git a/ReflexToQ3/includes/EntityConverter.cpp b/ReflexToQ3/includes/EntityConverter.cpp index da3e24e..3da30cc 100644 --- a/ReflexToQ3/includes/EntityConverter.cpp +++ b/ReflexToQ3/includes/EntityConverter.cpp @@ -32,8 +32,15 @@ EntityConverter::EntityConverter(std::string entityMapFile) : OFFSET_PLAYER(32.0), OFFSET_PICKUP(2.0) { - //MUST RUN matchRelated method after this constructor - areEntitiesMatched_ = false; + //MUST RUN extractMapInfo method after this constructor + haveMapInfo_ = false; + // game modes default to enabled + mapinfo_.cts = true; + mapinfo_.ctf = true; + mapinfo_.ffa = true; + mapinfo_.tdm = true; + mapinfo_.duel = true; + mapEntities(entityMapFile); } @@ -41,9 +48,17 @@ EntityConverter::EntityConverter(std::string entityMapFile) : OFFSET_PLAYER(32.0 EntityConverter::EntityConverter(std::string entityMapFile, std::string reflexMapFile) : OFFSET_PLAYER(32.0), OFFSET_PICKUP(2.0) { + haveMapInfo_ = false; + // game modes default to enabled + mapinfo_.cts = true; + mapinfo_.ctf = true; + mapinfo_.ffa = true; + mapinfo_.tdm = true; + mapinfo_.duel = true; + mapEntities(entityMapFile); - // Pre-scan for related entities + // Pre-scan for info needed by converter std::ifstream fin; fin.open(reflexMapFile); @@ -51,14 +66,14 @@ EntityConverter::EntityConverter(std::string entityMapFile, std::string reflexMa //Extract the source type of targets (teleporters or jump pads) std::string line; while (std::getline(fin, line)) { - addIfRelated(line, fin); + extractFromEntity(line, fin); } } else { throw std::ios::failure( "Error: EntityConverter failed to open .map file" ); } fin.close(); - areEntitiesMatched_ = true; + haveMapInfo_ = true; //DEBUG //printMapping(); @@ -70,17 +85,17 @@ EntityConverter::EntityConverter(std::string entityMapFile, std::string reflexMa /* *-------------------------------------------------------------------------------------- * Class: EntityConverter - * Method: EntityConverter :: matchRelated + * Method: EntityConverter :: extractMapInfo * Description: Read through entities, matching related as necessary * Note: For now, accomplishes the same goal as the pre-scan * constructor *-------------------------------------------------------------------------------------- */ void -EntityConverter::matchRelated(std::queue> entities) +EntityConverter::extractMapInfo(std::queue> entities) { - if( areEntitiesMatched_ ) { - std::cerr << "Related entities are already matched, doing nothing" << std::endl; + if( haveMapInfo_ ) { + std::cerr << "Map info already extracted, doing nothing" << std::endl; } else { while ( ! entities.empty() ) { @@ -93,12 +108,12 @@ EntityConverter::matchRelated(std::queue> entities) std::string nextLine; if ( getline(ss, nextLine )) { - addIfRelated(nextLine, ss); + extractFromEntity(nextLine, ss); } } } - areEntitiesMatched_ = true; + haveMapInfo_ = true; } @@ -106,7 +121,7 @@ EntityConverter::matchRelated(std::queue> entities) std::vector EntityConverter::convert(std::vector lines) { - if ( areEntitiesMatched_ ) + if ( haveMapInfo_ ) { std::string attribute; std::string trash; //unused tokens @@ -145,7 +160,7 @@ EntityConverter::convert(std::vector lines) } } else { - throw std::runtime_error( "error: related entities must be matched prior to conversion" ); + throw std::runtime_error( "error: Map info must be extracted prior to conversion" ); } // If unsupported entity, return empty vector @@ -260,7 +275,7 @@ EntityConverter::convertPlayerSpawn(std::vector &lines) } } else if ( type == "modeRace" ) { - isModeRace = false; + isModeRace = false; // Bool8 modeRace 0 indicates this spawn is not for race } else if ( type == "teamA" ) { team = 2; // Bool8 teamA 0 indicates teamB only @@ -271,7 +286,14 @@ EntityConverter::convertPlayerSpawn(std::vector &lines) } if ( havePosition ) { - if ( ! isModeRace ) { + if ( mapinfo_.cts && isModeRace ) { + convertedLines.push_back ( "\"classname\" \"info_player_race\"\n" ); + // Reflex maps have only start and finish, point to start on spawn + convertedLines.push_back ( "\"target\" \"cp1\"\n" ); + // Reflex maps are only cts, set spawn to cts-only type + convertedLines.push_back ( "\"race_place\" \"-1\"\n" ); + } + else { switch (team) { case 0: convertedLines.push_back ( "\"classname\" \"info_player_deathmatch\"\n" ); @@ -283,15 +305,8 @@ EntityConverter::convertPlayerSpawn(std::vector &lines) convertedLines.push_back ( "\"classname\" \"info_player_team2\"\n" ); break; } + } - } - else { - convertedLines.push_back ( "\"classname\" \"info_player_race\"\n" ); - // Reflex maps have only start and finish, point to start on spawn - convertedLines.push_back ( "\"target\" \"cp1\"\n" ); - // Reflex maps are only cts, set spawn to cts-only type - convertedLines.push_back ( "\"race_place\" \"-1\"\n" ); - } std::stringstream oss; // coordinates reordered to x, z, y @@ -519,7 +534,7 @@ EntityConverter::mapEntities(std::string mapFile) void -EntityConverter::addIfRelated(std::string &line, std::istream &is) +EntityConverter::extractFromEntity(std::string &line, std::istream &is) { std::string trash; std::string targetName; @@ -539,6 +554,26 @@ EntityConverter::addIfRelated(std::string &line, std::istream &is) } targetMap_.insert ( std::pair(targetName, "JumpPad") ); } + else if ( line.find("type WorldSpawn") != std::string::npos) { + while ( std::getline(is, line)) { + // only works for maps that support race AND other modes + if ( line.find("modeRace 0") != std::string::npos) { + mapinfo_.cts = false; + } + else if ( line.find("modeCTF 0") != std::string::npos) { + mapinfo_.ctf = false; + } + else if ( line.find("modeFFA 0") != std::string::npos) { + mapinfo_.ffa = false; + } + else if ( line.find("modeTDM 0") != std::string::npos) { + mapinfo_.tdm = false; + } + else if ( line.find("mode1v1 0") != std::string::npos) { + mapinfo_.duel = false; + } + } + } } diff --git a/ReflexToQ3/includes/EntityConverter.hpp b/ReflexToQ3/includes/EntityConverter.hpp index ce6145d..96aabf3 100644 --- a/ReflexToQ3/includes/EntityConverter.hpp +++ b/ReflexToQ3/includes/EntityConverter.hpp @@ -35,6 +35,19 @@ + + +struct MapInfo +{ + bool cts; + bool ctf; + bool ffa; + bool tdm; + bool duel; +}; + + + class EntityConverter { public: @@ -43,7 +56,7 @@ class EntityConverter * Class: EntityConverter * Method: Constructor * Description: Creates entity format mapping - * CAUTION: Requires matchRelated method to be called after this + * CAUTION: Requires extractMapInfo method to be called after this * Requires: .ent filename for mapping entities from reflex format to xonotic format * THROWS: runtime_error on .ent format error * THROWS: std::ios::failure on IO failure @@ -53,7 +66,7 @@ class EntityConverter /* *-------------------------------------------------------------------------------------- * Class: EntityConverter * Method: Constructor - * Description: Creates entity format mapping and pre-scans for related entities + * Description: Creates entity format mapping and pre-scans for map info * Parameter: string entityMapFile, file maps source to target entity formats * Parameter: string reflexMapFile, for pre-scan * THROWS: runtime_error on .ent format error @@ -70,21 +83,21 @@ class EntityConverter * Return: vector of strings, single entity in the converted format * *IF entity is not supported, returns EMPTY vector * THROWS: runtime_error on malformed .map file - * THROWS: runtime_error when called before related entities are matched + * THROWS: runtime_error when called before map info has been extracted *-------------------------------------------------------------------------------------- */ std::vector convert(std::vector lines); /* *-------------------------------------------------------------------------------------- * Class: EntityConverter - * Method: EntityConverter :: matchRelated - * Description: Finds related entities (targets of teleports, etc), call after parsing - * the entire .map + * Method: EntityConverter :: extractMapInfo + * Description: Get information needed by the converter that can't be obtained + * in entity-by-entity conversion (teleport and jump pad * Parameter: queue of vector of string entities, ALL entities in a .map file * THROWS: runtime_error when encountering malformed entity *-------------------------------------------------------------------------------------- */ - void matchRelated(std::queue> entities); + void extractMapInfo(std::queue> entities); @@ -110,12 +123,14 @@ class EntityConverter - // Related entities must be matched prior to entity conversion - bool areEntitiesMatched_; // Map Reflex pickup IDs to Xonotic pickup identifiers std::map pickupMapping_; // Map targets (by name) to their source type std::map targetMap_; + // Related entities must be matched prior to entity conversion + bool haveMapInfo_; + MapInfo mapinfo_; + // Offsets for item/spawn height const float OFFSET_PLAYER; const float OFFSET_PICKUP; @@ -145,14 +160,14 @@ class EntityConverter /* *-------------------------------------------------------------------------------------- * Class: EntityConverter - * Method: EntityConverter :: addIfRelated - * Description: If the entity contains a related target/etc, add to map + * Method: EntityConverter :: extractFromEntity + * Description: Get map info from a single entity * Paramater: string line, the previous line (contains entity type) * Parameter: istream is, an ifstream or a stringstream containing a * single entity *-------------------------------------------------------------------------------------- */ - void addIfRelated(std::string &line, std::istream &is); + void extractFromEntity(std::string &line, std::istream &is); /* *-------------------------------------------------------------------------------------- * Class: EntityConverter diff --git a/ReflexToQ3/test/catch.cpp b/ReflexToQ3/test/catch.cpp index 97b0067..292f7d3 100644 --- a/ReflexToQ3/test/catch.cpp +++ b/ReflexToQ3/test/catch.cpp @@ -44,7 +44,7 @@ TEST_CASE( "r2x: Unsupported entity types cause return of empty vector", "[Entit q.push( entity ); // Match related entities (none) - ec.matchRelated( q ); + ec.extractMapInfo( q ); // Convert a single entity std::vector converted = ec.convert(entity); @@ -71,7 +71,7 @@ TEST_CASE( "r2x: a single Pickup entity can be converted", "[EntityConverter]" ) q.push( entity ); // Match related entities (none) - ec.matchRelated( q ); + ec.extractMapInfo( q ); // Convert a single entity std::vector converted = ec.convert(entity); @@ -110,12 +110,14 @@ TEST_CASE( "r2x: a single PlayerSpawn (race) entity can be converted", "[EntityC entity.push_back(" Bool8 modeTDM 0"); entity.push_back(" Bool8 mode1v1 0"); + // With how map info works, at least one other spawn must be + // Mock up entity queue std::queue> q; q.push( entity ); // Match related entities (none) - ec.matchRelated( q ); + ec.extractMapInfo( q ); // Convert a single entity std::vector converted = ec.convert(entity); @@ -159,7 +161,7 @@ TEST_CASE( "r2x: a single PlayerSpawn (teamA) entity can be converted", "[Entity q.push( entity ); // Match related entities (none) - ec.matchRelated( q ); + ec.extractMapInfo( q ); // Convert a single entity std::vector converted = ec.convert(entity); @@ -182,6 +184,46 @@ TEST_CASE( "r2x: a single PlayerSpawn (teamA) entity can be converted", "[Entity +TEST_CASE( "r2x: a single PlayerSpawn (non-team) entity can be converted", "[EntityConverter]" ) { + + // Instantiate object + EntityConverter ec (PICKUP_FILENAME); + + // Mock up entity + std::vector entity; + entity.push_back(" type PlayerSpawn"); + entity.push_back(" Vector3 position -216.00000 -132.00000 -1488.000488"); + entity.push_back(" Vector3 angles 180.00000 0.00000 0.00000"); + + // Mock up entity queue + std::queue> q; + q.push( entity ); + + // Match related entities (none) + ec.extractMapInfo( q ); + + // Convert a single entity + std::vector converted = ec.convert(entity); + + REQUIRE( converted[0] == "\"classname\" \"info_player_deathmatch\"\n" ); + REQUIRE( converted[2] == "\"angle\" \"180.00000\"\n" ); + + // The z (vertical) is offset by +32 + std::istringstream iss(converted[1]); + std::string attribute; + std::string coords[2]; + float offsetCoord; + iss >> attribute >> coords[0] >> coords[1] >> offsetCoord; + + REQUIRE( attribute == "\"origin\"" ); + REQUIRE( coords[0] == "\"-216.00000" ); + REQUIRE( coords[1] == "-1488.000488" ); + REQUIRE( fabs(-100.00000 - offsetCoord) <= DELTA ); +} + + + + TEST_CASE( "r2x: a single RaceStart entity can be converted", "[EntityConverter]" ) { // Instantiate object @@ -196,7 +238,7 @@ TEST_CASE( "r2x: a single RaceStart entity can be converted", "[EntityConverter] q.push( entity ); // Match related entities (none) - ec.matchRelated( q ); + ec.extractMapInfo( q ); // Convert a single entity std::vector converted = ec.convert(entity); @@ -222,7 +264,7 @@ TEST_CASE( "r2x: a single RaceFinish entity can be converted", "[EntityConverter q.push( entity ); // Match related entities (none) - ec.matchRelated( q ); + ec.extractMapInfo( q ); // Convert a single entity std::vector converted = ec.convert(entity); @@ -256,7 +298,7 @@ TEST_CASE( "r2x: a single Teleporter and related Target can be converted", "[Ent q.push( entity2 ); // Match related entities (one pair) - ec.matchRelated( q ); + ec.extractMapInfo( q ); // Convert two entities std::vector converted = ec.convert(entity); @@ -308,7 +350,7 @@ TEST_CASE( "r2x: a single JumpPad and related Target can be converted", "[Entity q.push( entity2 ); // Match related entities (one pair) - ec.matchRelated( q ); + ec.extractMapInfo( q ); // Convert two entities std::vector converted = ec.convert(entity);