diff --git a/ReflexToQ3/Makefile b/ReflexToQ3/Makefile index 097d470..701b505 100644 --- a/ReflexToQ3/Makefile +++ b/ReflexToQ3/Makefile @@ -4,7 +4,7 @@ CFLAGS=-std=c++11 -I"./includes" -I"./includes/Catch/single_include" -I"./includ TESTEX=test/test-parser UNITEX=test/catch -all: main +all: main unittest main: planes.o brushdef.o oopless-parser.o EntityConverter.o $(CC) $^ main.cpp $(CFLAGS) -o $(EX) 2>error8.log diff --git a/ReflexToQ3/includes/EntityConverter.cpp b/ReflexToQ3/includes/EntityConverter.cpp index da3e24e..f5fd1da 100644 --- a/ReflexToQ3/includes/EntityConverter.cpp +++ b/ReflexToQ3/includes/EntityConverter.cpp @@ -25,25 +25,42 @@ #include - /*----------------------------------------------------------------------------- * PUBLIC *-----------------------------------------------------------------------------*/ -EntityConverter::EntityConverter(std::string entityMapFile) : OFFSET_PLAYER(32.0), OFFSET_PICKUP(2.0) +EntityConverter::EntityConverter(const std::string &entityMapFile) : + OFFSET_PLAYER(32.0), OFFSET_PICKUP(2.0), BRIGHTNESS_ADJUST(50.0) { - //MUST RUN matchRelated method after this constructor - areEntitiesMatched_ = false; + //MUST RUN extractMapInfo method after this constructor + haveMapInfo_ = false; + // game modes default to enabled + ws_.cts = true; + ws_.ctf = true; + ws_.ffa = true; + ws_.tdm = true; + ws_.duel = true; + mapEntities(entityMapFile); } -EntityConverter::EntityConverter(std::string entityMapFile, std::string reflexMapFile) : OFFSET_PLAYER(32.0), OFFSET_PICKUP(2.0) +EntityConverter::EntityConverter(const std::string &entityMapFile, + const std::string &reflexMapFile) : OFFSET_PLAYER(32.0), + OFFSET_PICKUP(2.0), BRIGHTNESS_ADJUST(50.0) { + haveMapInfo_ = false; + // game modes default to enabled + ws_.cts = true; + ws_.ctf = true; + ws_.ffa = true; + ws_.tdm = true; + ws_.duel = true; + mapEntities(entityMapFile); - // Pre-scan for related entities + // Pre-scan for info needed by converter std::ifstream fin; fin.open(reflexMapFile); @@ -51,14 +68,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(); @@ -67,24 +84,15 @@ EntityConverter::EntityConverter(std::string entityMapFile, std::string reflexMa -/* - *-------------------------------------------------------------------------------------- - * Class: EntityConverter - * Method: EntityConverter :: matchRelated - * 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() ) { - std::vector entity = entities.front(); + std::vector entity( entities.front() ); entities.pop(); std::stringstream ss; @@ -93,20 +101,47 @@ EntityConverter::matchRelated(std::queue> entities) std::string nextLine; if ( getline(ss, nextLine )) { - addIfRelated(nextLine, ss); + extractFromEntity(nextLine, ss); } } } - areEntitiesMatched_ = true; + haveMapInfo_ = true; +} + + + +void +EntityConverter::extractMapInfo(const std::vector> &entities) +{ + if( haveMapInfo_ ) { + std::cerr << "Map info already extracted, doing nothing" << std::endl; + } + else { + std::vector>::const_iterator it; + for ( it=entities.begin(); it!=entities.end(); ++it ) { + std::vector entity( *it ); + + std::stringstream ss; + std::copy(entity.begin(), entity.end(), + std::ostream_iterator(ss, "\n")); + + std::string nextLine; + if ( getline(ss, nextLine )) { + extractFromEntity(nextLine, ss); + } + } + } + + haveMapInfo_ = true; } std::vector -EntityConverter::convert(std::vector lines) +EntityConverter::convert(const std::vector &lines) { - if ( areEntitiesMatched_ ) + if ( haveMapInfo_ ) { std::string attribute; std::string trash; //unused tokens @@ -121,8 +156,37 @@ EntityConverter::convert(std::vector lines) throw std::runtime_error("error: type is required"); } + // If worldspawn, first reenable all gamemodes + // then check worldspawn for disabled modes + // then RETURN EMPTY VECTOR + if ( type == "WorldSpawn" ) { + ws_.cts = true; + ws_.ctf = true; + ws_.ffa = true; + ws_.tdm = true; + ws_.duel = true; - if ( type == "Pickup" ) { + // Each worldspawn can specify modes enabled/disabled + for ( int i = 1; i < lines.size(); ++i ) { + if ( lines[i].find("modeRace 0") != std::string::npos) { + ws_.cts = false; + } + else if ( lines[i].find("modeCTF 0") != std::string::npos) { + ws_.ctf = false; + } + else if ( lines[i].find("modeFFA 0") != std::string::npos) { + ws_.ffa = false; + } + else if ( lines[i].find("modeTDM 0") != std::string::npos) { + ws_.tdm = false; + } + else if ( lines[i].find("mode1v1 0") != std::string::npos) { + ws_.duel = false; + } + } + + } + else if ( type == "Pickup" ) { return convertPickup(lines); } else if ( type == "PlayerSpawn" ) { @@ -143,9 +207,12 @@ EntityConverter::convert(std::vector lines) else if ( type == "RaceFinish" ) { return convertRaceFinish(lines); } + else if ( type == "PointLight" ) { + return convertPointLight(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 @@ -159,7 +226,7 @@ EntityConverter::convert(std::vector lines) * PROTECTED *-----------------------------------------------------------------------------*/ std::vector -EntityConverter::convertPickup(std::vector &lines) +EntityConverter::convertPickup(const std::vector &lines) const { std::vector convertedLines; //can ignore angle of pickups in xonotic format @@ -195,14 +262,14 @@ EntityConverter::convertPickup(std::vector &lines) } if ( havePosition && havePickupID ) { - std::stringstream oss; - oss << "\"classname\" \"" << pickupMapping_[pickupID] << "\"" << std::endl; - convertedLines.push_back ( oss.str() ); + std::stringstream pickupStream; + pickupStream << "\"classname\" \"" << pickupMapping_.find(pickupID)->second << "\"" << std::endl; + convertedLines.push_back ( pickupStream.str() ); // coordinates reordered to x, z, y - std::stringstream oss2; - oss2 << "\"origin\" \"" << coords[0] << " " << coords[2] << " " << + std::stringstream positionStream; + positionStream << "\"origin\" \"" << coords[0] << " " << coords[2] << " " << offset(coords[1], OFFSET_PICKUP) << "\"" << std::endl; - convertedLines.push_back ( oss2.str() ); + convertedLines.push_back ( positionStream.str() ); return convertedLines; } else { @@ -223,7 +290,7 @@ EntityConverter::convertPickup(std::vector &lines) *-------------------------------------------------------------------------------------- */ std::vector -EntityConverter::convertPlayerSpawn(std::vector &lines) +EntityConverter::convertPlayerSpawn(const std::vector &lines) const { std::vector convertedLines; // Requires position coordinate @@ -234,7 +301,11 @@ EntityConverter::convertPlayerSpawn(std::vector &lines) int team = 0; std::string trash; bool havePosition = false; - bool isModeRace = true; + bool isModeRace = ws_.cts; + bool isModeCtf = ws_.ctf; + bool isModeTdm = ws_.tdm; + bool isModeFfa = ws_.ffa; + bool isModeDuel = ws_.duel; if ( lines.size() < 2 ) { @@ -259,8 +330,21 @@ EntityConverter::convertPlayerSpawn(std::vector &lines) throw std::runtime_error("error: Pickup entity requires Pickup ID"); } } + // Bool8 modeX 0 indicates this spawn is not for game mode X else if ( type == "modeRace" ) { - isModeRace = false; + isModeRace = false; + } + else if ( type == "modeCTF" ) { + isModeCtf = false; + } + else if ( type == "modeTDM" ) { + isModeTdm = false; + } + else if ( type == "modeFFA" ) { + isModeFfa = false; + } + else if ( type == "mode1v1" ) { + isModeDuel = false; } else if ( type == "teamA" ) { team = 2; // Bool8 teamA 0 indicates teamB only @@ -271,7 +355,8 @@ EntityConverter::convertPlayerSpawn(std::vector &lines) } if ( havePosition ) { - if ( ! isModeRace ) { + // Will convert all race points to dm/team spawns on maps that support race AND others + if ( isModeCtf || isModeTdm || isModeFfa || isModeDuel ) { switch (team) { case 0: convertedLines.push_back ( "\"classname\" \"info_player_deathmatch\"\n" ); @@ -283,7 +368,6 @@ EntityConverter::convertPlayerSpawn(std::vector &lines) convertedLines.push_back ( "\"classname\" \"info_player_team2\"\n" ); break; } - } else { convertedLines.push_back ( "\"classname\" \"info_player_race\"\n" ); @@ -293,14 +377,14 @@ EntityConverter::convertPlayerSpawn(std::vector &lines) convertedLines.push_back ( "\"race_place\" \"-1\"\n" ); } - std::stringstream oss; + std::stringstream positionStream; // coordinates reordered to x, z, y - oss << "\"origin\" \"" << coords[0] << " " << coords[2] << " " << + positionStream << "\"origin\" \"" << coords[0] << " " << coords[2] << " " << offset(coords[1], OFFSET_PLAYER) << "\"" << std::endl; - convertedLines.push_back ( oss.str() ); - std::stringstream oss2; - oss2 << "\"angle\" \"" << angle << "\"" << std::endl; - convertedLines.push_back ( oss2.str() ); + convertedLines.push_back ( positionStream.str() ); + std::stringstream angleStream; + angleStream << "\"angle\" \"" << angle << "\"" << std::endl; + convertedLines.push_back (angleStream.str() ); return convertedLines; } else { @@ -311,7 +395,7 @@ EntityConverter::convertPlayerSpawn(std::vector &lines) std::vector -EntityConverter::convertJumpPad(std::vector &lines) +EntityConverter::convertJumpPad(const std::vector &lines) const { std::vector convertedLines; std::string targetName; @@ -336,7 +420,7 @@ EntityConverter::convertJumpPad(std::vector &lines) std::vector -EntityConverter::convertTeleporter(std::vector &lines) +EntityConverter::convertTeleporter(const std::vector &lines) const { std::vector convertedLines; std::string targetName; @@ -361,7 +445,7 @@ EntityConverter::convertTeleporter(std::vector &lines) std::vector -EntityConverter::convertTarget(std::vector &lines) +EntityConverter::convertTarget(const std::vector &lines) const { std::vector convertedLines; //position and name required, angles optional @@ -409,7 +493,7 @@ EntityConverter::convertTarget(std::vector &lines) } if ( havePosition && haveName) { - if ( targetMap_[targetName] == "Teleporter") { + if ( targetMap_.find(targetName)->second == "Teleporter") { convertedLines.push_back ( "\"classname\" \"misc_teleporter_dest\"\n" ); // coordinates reordered to x, z, y // teleporter height is OFFSET @@ -418,7 +502,7 @@ EntityConverter::convertTarget(std::vector &lines) offset(coords[1], OFFSET_PLAYER) << "\"" << std::endl; convertedLines.push_back ( oss.str() ); } - else if ( targetMap_[targetName] == "JumpPad") { + else if ( targetMap_.find(targetName)->second == "JumpPad") { convertedLines.push_back ( "\"classname\" \"target_position\"\n" ); // coordinates reordered to x, z, y std::stringstream oss; @@ -426,15 +510,15 @@ EntityConverter::convertTarget(std::vector &lines) coords[1] << "\"" << std::endl; convertedLines.push_back ( oss.str() ); } - std::stringstream oss; - oss << "\"targetname\" \"" << targetName << "\"" << std::endl; - convertedLines.push_back ( oss.str() ); + std::stringstream targetStream; + targetStream << "\"targetname\" \"" << targetName << "\"" << std::endl; + convertedLines.push_back ( targetStream.str() ); // Write angle only if position and name exist if ( haveAngle ) { - std::stringstream oss2; - oss2 << "\"angle\" \"" << angle << "\"" << std::endl; - convertedLines.push_back (oss2.str() ); + std::stringstream angleStream; + angleStream << "\"angle\" \"" << angle << "\"" << std::endl; + convertedLines.push_back( angleStream.str() ); } return convertedLines; } @@ -447,7 +531,7 @@ EntityConverter::convertTarget(std::vector &lines) std::vector -EntityConverter::convertRaceStart(std::vector &lines) +EntityConverter::convertRaceStart(const std::vector &lines) const { std::vector convertedLines; convertedLines.push_back ("\"classname\" \"trigger_race_checkpoint\"\n"); @@ -459,7 +543,7 @@ EntityConverter::convertRaceStart(std::vector &lines) std::vector -EntityConverter::convertRaceFinish(std::vector &lines) +EntityConverter::convertRaceFinish(const std::vector &lines) const { std::vector convertedLines; convertedLines.push_back ("\"classname\" \"trigger_race_checkpoint\"\n"); @@ -470,12 +554,91 @@ EntityConverter::convertRaceFinish(std::vector &lines) +std::vector +EntityConverter::convertPointLight(const std::vector &lines) const +{ + std::vector convertedLines; + //position and intensity required, color optional + std::string coords[3]; + std::string intensity; + //color is hex 8 digits + std::string color; + std::string trash; + bool havePosition = false; + bool haveIntensity = false; + bool haveColor = false; + + + if ( lines.size() < 3 ) { + throw std::runtime_error("error: PointLight entity requires at least 3 lines"); + } + + for (int i = 1; i < lines.size(); i++) { + std::string type = getAttributeType(lines[i]); + if ( type == "position" ) { + std::istringstream iss(lines[i]); + // Vector3 position coord0 coord1 coord2 + if ( ! (iss >> trash >> trash >> + coords[0] >> coords[1] >> coords[2])) { + throw std::runtime_error( "error: PointLight entity requires position coordinates" ); + } + havePosition = true; + } + else if ( type == "intensity" ) { + std::istringstream iss(lines[i]); + // Float intensity validFloat + if ( ! (iss >> trash >> trash >> intensity) ) { + throw std::runtime_error( "error: PointLight entity requires intensity value" ); + } + haveIntensity = true; + } + else if ( type == "color" ) { + std::istringstream iss(lines[i]); + // ColourXRGB32 color eightDigitHexValue + if ( ! (iss >> trash >> trash >> color) ) { + throw std::runtime_error( "error: PointLight entity requires valid float value if specified" ); + } + haveColor = true; + } + + } + + + if ( havePosition && haveIntensity) { + convertedLines.push_back ( "\"classname\" \"light\"\n" ); + // coordinates reordered to x, z, y + std::stringstream positionStream; + positionStream << "\"origin\" \"" << coords[0] << " " << coords[2] << " " << + coords[1] << "\"" << std::endl; + convertedLines.push_back ( positionStream.str() ); + // convert intensity + std::stringstream intensityStream; + intensityStream << "\"light\" \"" << adjustBrightness(intensity) << "\"\n"; + convertedLines.push_back ( intensityStream.str() ); + + if ( haveColor ) { + std::stringstream colorStream; + float red; + float green; + float blue; + // Convert 32bit hex value into RGB values + hexToRGB(color, red, green, blue); + colorStream << "\"_color\" \"" << red << " " << green << " " << blue << "\"" << std::endl; + convertedLines.push_back (colorStream.str() ); + } + return convertedLines; + } + else { + throw std::runtime_error("error: Target entity requires position coordinates and targetname"); + } + +} /*----------------------------------------------------------------------------- * PRIVATE *-----------------------------------------------------------------------------*/ std::string -EntityConverter::getAttributeType(std::string line) +EntityConverter::getAttributeType(const std::string &line) const { std::string type; std::string dataType; @@ -490,7 +653,7 @@ EntityConverter::getAttributeType(std::string line) void -EntityConverter::mapEntities(std::string mapFile) +EntityConverter::mapEntities(const std::string &mapFile) { std::ifstream fin; fin.open(mapFile); @@ -519,32 +682,34 @@ EntityConverter::mapEntities(std::string mapFile) void -EntityConverter::addIfRelated(std::string &line, std::istream &is) +EntityConverter::extractFromEntity(const std::string &line, std::istream &is) { std::string trash; std::string targetName; + std::string nextLine; if ( line.find("type Teleporter") != std::string::npos) { - std::getline(is, line); - std::istringstream iss(line); + std::getline(is, nextLine); + std::istringstream iss(nextLine); if ( ! (iss >> trash >> trash >> targetName)) { throw std::runtime_error( "format error in .map file"); } targetMap_.insert ( std::pair(targetName, "Teleporter") ); } else if ( line.find("type JumpPad") != std::string::npos) { - std::getline(is, line); - std::istringstream iss(line); + std::getline(is, nextLine); + std::istringstream iss(nextLine); if ( ! (iss >> trash >> trash >> targetName)) { throw std::runtime_error( "format error in .map file"); } targetMap_.insert ( std::pair(targetName, "JumpPad") ); } - } -std::string EntityConverter::offset(std::string value, float amount) { +std::string +EntityConverter::offset(const std::string &value, const float amount) const +{ std::istringstream iss(value); float c; iss >> c; @@ -557,22 +722,51 @@ std::string EntityConverter::offset(std::string value, float amount) { +void +EntityConverter::hexToRGB(const std::string &hex, float &r, float &g, float &b) const +{ + unsigned int value; + std::stringstream ss; + ss << std::hex << hex; + ss >> value; + + // get the necessary components from the top 3 bytes + r = ((value >> 16) & 0xFF) / 255.0; + g = ((value >> 8) & 0xFF) / 255.0; + b = ((value) & 0xFF) / 255.0; +} + + + +int +EntityConverter::adjustBrightness(const std::string &value) const +{ + float inputBright; + std::stringstream ss(value); + ss >> inputBright; + + return static_cast(inputBright * BRIGHTNESS_ADJUST); +} + + // DEBUG void -EntityConverter::printMapping() +EntityConverter::printMapping() const { - std::map::iterator it; - for (it=pickupMapping_.begin(); it!=pickupMapping_.end(); ++it) + std::cout << std::endl << "Reflex pickup ID mapped to Xonotic pickup names: " << std::endl; + std::map::const_iterator it; + for ( it=pickupMapping_.begin(); it!=pickupMapping_.end(); ++it ) std::cout << it->first << " => " << it->second << std::endl; } // DEBUG void -EntityConverter::printTargetSources() +EntityConverter::printTargetSources() const { - std::map::iterator it; - for (it=targetMap_.begin(); it!=targetMap_.end(); ++it) + std::cout << std::endl << "Target and Sources: " << std::endl; + std::map::const_iterator it; + for ( it=targetMap_.begin(); it!=targetMap_.end(); ++it ) std::cout << it->first << " => " << it->second << std::endl; } diff --git a/ReflexToQ3/includes/EntityConverter.hpp b/ReflexToQ3/includes/EntityConverter.hpp index ce6145d..52cce09 100644 --- a/ReflexToQ3/includes/EntityConverter.hpp +++ b/ReflexToQ3/includes/EntityConverter.hpp @@ -35,6 +35,19 @@ + + +struct WorldSpawn +{ + bool cts; + bool ctf; + bool ffa; + bool tdm; + bool duel; +}; + + + class EntityConverter { public: @@ -43,24 +56,24 @@ 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 *-------------------------------------------------------------------------------------- */ - EntityConverter(std::string entityMapFile); + EntityConverter(const std::string &entityMapFile); /* *-------------------------------------------------------------------------------------- * 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 * THROWS: std::ios::failure on IO failure *-------------------------------------------------------------------------------------- */ - EntityConverter(std::string entityMapFile, std::string reflexMapFile); + EntityConverter(const std::string &entityMapFile, const std::string &reflexMapFile); /* *-------------------------------------------------------------------------------------- * Class: EntityConverter @@ -70,21 +83,32 @@ 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); + std::vector convert(const 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); + /* + *-------------------------------------------------------------------------------------- + * Class: EntityConverter + * 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: vector of vector of string entities, ALL entities in a .map file + * THROWS: runtime_error when encountering malformed entity + *-------------------------------------------------------------------------------------- + */ + void extractMapInfo(const std::vector> &entities); @@ -96,29 +120,34 @@ class EntityConverter * Class: EntityConverter * Method: EntityConverter :: convert~EntityName~ * Description: Multiple methods to convert entity from reflex to xonotic format - * Parameter: vector of strings entity, multi-lined entity + * Parameter: vector of strings lines, multi-lined entity * Return: vector of strings, the converted entity *-------------------------------------------------------------------------------------- */ - std::vector convertPickup(std::vector &entity); - std::vector convertPlayerSpawn(std::vector &entity); - std::vector convertJumpPad(std::vector &entity); - std::vector convertTeleporter(std::vector &entity); - std::vector convertTarget(std::vector &entity); - std::vector convertRaceStart(std::vector &entity); - std::vector convertRaceFinish(std::vector &entity); + std::vector convertPickup(const std::vector &lines) const; + std::vector convertPlayerSpawn(const std::vector &lines) const; + std::vector convertJumpPad(const std::vector &lines) const; + std::vector convertTeleporter(const std::vector &lines) const; + std::vector convertTarget(const std::vector &lines) const; + std::vector convertRaceStart(const std::vector &lines) const; + std::vector convertRaceFinish(const std::vector &lines) const; + std::vector convertPointLight(const std::vector &lines) const; - // 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_; + WorldSpawn ws_; + // Offsets for item/spawn height const float OFFSET_PLAYER; const float OFFSET_PICKUP; + // Brightness adjustment factor + const float BRIGHTNESS_ADJUST; @@ -131,7 +160,7 @@ class EntityConverter * Parameter: string "line", entity keyword followed by the type *-------------------------------------------------------------------------------------- */ - std::string getAttributeType(std::string line); + std::string getAttributeType(const std::string &line) const; /* *-------------------------------------------------------------------------------------- * Class: EntityConverter @@ -141,18 +170,18 @@ class EntityConverter * Return: true if no error, false if error *-------------------------------------------------------------------------------------- */ - void mapEntities(std::string mapFile); + void mapEntities(const std::string &mapFile); /* *-------------------------------------------------------------------------------------- * 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(const std::string &line, std::istream &is); /* *-------------------------------------------------------------------------------------- * Class: EntityConverter @@ -164,12 +193,33 @@ class EntityConverter * Return: string, float value passed as string *-------------------------------------------------------------------------------------- */ - std::string offset(std::string value, float offset); + std::string offset(const std::string &value, const float offset) const; + /* + *-------------------------------------------------------------------------------------- + * Class: EntityConverter + * Method: EntityConverter :: hexToRGB + * Description: Convert 8 digit hex value into separate red, green, and blue values + * Parameter: string hex, inputted hex value + * Parameter: float r, RETURN BY REFERENCE: converted red value + * Parameter: float g, RETURN BY REFERENCE: converted green value + * Parameter: float b, RETURN BY REFERENCE: converted blue value + *-------------------------------------------------------------------------------------- + */ + void hexToRGB(const std::string &hex, float &r, float &g, float &b) const; + /* + *-------------------------------------------------------------------------------------- + * Class: EntityConverter + * Method: EntityConverter :: adjustBrightness + * Description: Reflex uses significantly smaller values than Xonotic -> adjust + * Parameter: string value, original brightness value + *-------------------------------------------------------------------------------------- + */ + int adjustBrightness(const std::string &value) const; - void printMapping(); //DEBUG - void printTargetSources(); //DEBUG + void printMapping() const; //DEBUG + void printTargetSources() const; //DEBUG }; diff --git a/ReflexToQ3/r2x.pck b/ReflexToQ3/r2x.pck index 85f81ed..2edc648 100644 --- a/ReflexToQ3/r2x.pck +++ b/ReflexToQ3/r2x.pck @@ -1,11 +1,11 @@ 40 item_health_small 41 item_health_medium -42 item_health_large +42 item_health_big 43 item_health_mega 50 item_armor_small 51 item_armor_medium -52 item_armor_large -53 item_armor_big +52 item_armor_big +53 item_armor_mega 1 weapon_uzi 2 weapon_grenadelauncher 3 weapon_hagar diff --git a/ReflexToQ3/test/catch.cpp b/ReflexToQ3/test/catch.cpp index 97b0067..3eed42f 100644 --- a/ReflexToQ3/test/catch.cpp +++ b/ReflexToQ3/test/catch.cpp @@ -37,14 +37,14 @@ TEST_CASE( "r2x: Unsupported entity types cause return of empty vector", "[Entit // Mock up entity std::vector entity; - entity.push_back(" type Worldspawn"); + entity.push_back(" type NotAType"); // 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); @@ -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); @@ -98,6 +98,16 @@ TEST_CASE( "r2x: a single PlayerSpawn (race) entity can be converted", "[EntityC // Instantiate object EntityConverter ec (PICKUP_FILENAME); + // Mock up WorldSpawn entity + // (needed for mode info) + std::vector worldspawn; + worldspawn.push_back(" type WorldSpawn"); + worldspawn.push_back(" Bool8 modeCTF 0"); + worldspawn.push_back(" Bool8 modeFFA 0"); + worldspawn.push_back(" Bool8 modeTDM 0"); + worldspawn.push_back(" Bool8 mode1v1 0"); + + // Mock up entity std::vector entity; entity.push_back(" type PlayerSpawn"); @@ -112,12 +122,15 @@ TEST_CASE( "r2x: a single PlayerSpawn (race) entity can be converted", "[EntityC // Mock up entity queue std::queue> q; + q.push( worldspawn ); q.push( entity ); // Match related entities (none) - ec.matchRelated( q ); + ec.extractMapInfo( q ); - // Convert a single entity + // Convert a single entity (worldspawn conversion returns empty vector + // BUT sets the supported game modes for entities in that worldspawn + std::vector unused = ec.convert(worldspawn); std::vector converted = ec.convert(entity); REQUIRE( converted[0] == "\"classname\" \"info_player_race\"\n" ); @@ -136,6 +149,20 @@ TEST_CASE( "r2x: a single PlayerSpawn (race) entity can be converted", "[EntityC REQUIRE( coords[0] == "\"-216.00000" ); REQUIRE( coords[1] == "-1488.000488" ); REQUIRE( fabs(-100.00000 - offsetCoord) <= DELTA ); + + SECTION( "Encountering a new worldspawn reenables all modes" ) { + std::vector basicWorldspawn; + basicWorldspawn.push_back(" type WorldSpawn"); + + std::vector e; + e.push_back(" type PlayerSpawn"); + e.push_back(" Vector3 position -216.00000 -132.00000 -1488.000488"); + e.push_back(" Vector3 angles 180.00000 0.00000 0.00000"); + + std::vector u = ec.convert(basicWorldspawn); + std::vector c = ec.convert(e); + REQUIRE( c[0] == "\"classname\" \"info_player_deathmatch\"\n" ); + } } @@ -159,7 +186,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 +209,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 +263,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 +289,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 +323,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); @@ -274,7 +341,7 @@ TEST_CASE( "r2x: a single Teleporter and related Target can be converted", "[Ent float offsetCoord; iss >> attribute >> coords[0] >> coords[1] >> offsetCoord; - // first test fails without busy wait + // next REQUIRE fails without busy wait for( int i = 0; i < 10000000; i++ ) int x = i; @@ -308,7 +375,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); @@ -323,6 +390,52 @@ TEST_CASE( "r2x: a single JumpPad and related Target can be converted", "[Entity +TEST_CASE( "r2x: a single PointLight entity can be converted", "[EntityConverter]" ) { + + // Instantiate object + EntityConverter ec (PICKUP_FILENAME); + + // Mock up entity + std::vector entity; + entity.push_back(" type PointLight"); + entity.push_back(" Vector3 position -216.00000 -132.00000 -1488.000488"); + entity.push_back(" ColourXRGB32 color ffffc400"); + entity.push_back(" Float intensity 1.500000"); + entity.push_back(" Float nearAttenuation 32.000000"); + entity.push_back(" Float farAttenuation 160.000000"); + + // 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\" \"light\"\n" ); + REQUIRE( converted[1] == "\"origin\" \"-216.00000 -1488.000488 -132.00000\"\n" ); + REQUIRE( converted[2] == "\"light\" \"75\"\n" ); + + INFO( converted[3] ); + std::istringstream iss(converted[3]); + std::string attribute; + std::string r; + float red; + float green; + float blue; + iss >> attribute >> r >> green >> blue; + r.erase(r.begin()); //removing preceding quote is necessary + std::stringstream redStream(r); + redStream >> red; + + REQUIRE( attribute == "\"_color\"" ); + + REQUIRE( fabs(1.0 - red) <= DELTA ); + REQUIRE( fabs(0.768627 - green) <= DELTA ); + REQUIRE( fabs(0.0 - blue) <= DELTA ); +}