/* * ===================================================================================== * * Filename: EntityConverter.cpp * * Description: Convert Reflex entities into Xonotic entities * * Version: 0.1 * Created: 06/05/2017 07:15:25 PM * Revision: none * Compiler: gcc * * Author: suhrke@teknik.io * * ===================================================================================== */ #include "EntityConverter.hpp" #include #include #include #include /*----------------------------------------------------------------------------- * PUBLIC *-----------------------------------------------------------------------------*/ EntityConverter::EntityConverter(std::string entityMapFile) { //MUST RUN matchRelated method after this constructor areEntitiesMatched_ = false; mapEntities(entityMapFile); } EntityConverter::EntityConverter(std::string entityMapFile, std::string reflexMapFile) { mapEntities(entityMapFile); // Pre-scan for related entities std::ifstream fin; fin.open(reflexMapFile); if ( fin.is_open() ) { //Extract the source type of targets (teleporters or jump pads) std::string line; std::string trash; std::string targetName; while (std::getline(fin, line)) { if ( line.find("type Teleporter") != std::string::npos) { std::getline(fin, line); std::istringstream iss(line); 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(fin, line); std::istringstream iss(line); if ( ! (iss >> trash >> trash >> targetName)) { throw std::runtime_error( "format error in .map file"); } targetMap_.insert ( std::pair(targetName, "JumpPad") ); } } } else { throw std::ios::failure( "Error: EntityConverter failed to open .map file" ); } fin.close(); areEntitiesMatched_ = true; //DEBUG //printMapping(); //printTargetSources(); } /* *-------------------------------------------------------------------------------------- * !-- Not sure if this is used the same way as the pre-scan was --! *-------------------------------------------------------------------------------------- */ void matchRelated(std::queue> entities) { if( areEntitiesMatched_ ) { std::cerr << "Related entities are already matched, doing nothing" << std::endl; } else { //Same as pre-scan or convert and pass back all converted entities? } } std::vector EntityConverter::convert(std::vector lines) { if ( areEntitiesMatched_ ) { std::string attribute; std::string trash; //unused tokens std::string type; if ( lines.size() < 1 ) { throw std::runtime_error("error: empty entity cannot be converted"); } // second token is the type std::istringstream iss(lines[0]); if ( ! (iss >> trash >> type)) { throw std::runtime_error("error: type is required"); } if ( type == "Pickup" ) { return convertPickup(lines); } else if ( type == "PlayerSpawn" ) { return convertPlayerSpawn(lines); } else if ( type == "JumpPad" ) { return convertJumpPad(lines); } else if ( type == "Teleporter" ) { return convertTeleporter(lines); } else if ( type == "Target" ) { return convertTarget(lines); } else if ( type == "RaceStart" ) { return convertRaceStart(lines); } else if ( type == "RaceFinish" ) { return convertRaceFinish(lines); } } else { throw std::runtime_error( "error: related entities must be matched prior to conversion" ); } } /*----------------------------------------------------------------------------- * PRIVATE *-----------------------------------------------------------------------------*/ std::string EntityConverter::getAttributeType(std::string line) { std::string type; std::string dataType; std::istringstream iss(line); if ( ! (iss >> dataType >> type )) { return std::string(); } return type; } void EntityConverter::mapEntities(std::string mapFile) { std::ifstream fin; fin.open(mapFile); if ( fin.is_open() ) { //Read .ent contents into pickup map std::string line; while (std::getline(fin, line)) { std::istringstream iss(line); // Reflex ID corresponds to xonotic pickup name int id; std::string pickup; if ( ! (iss >> id >> pickup)) { throw std::runtime_error( "format error in .ent file" ); } pickupMapping_.insert ( std::pair(id, pickup) ); } } else { throw std::ios::failure( "Error: EntityConverter failed to open .ent file" ); } fin.close(); } std::vector EntityConverter::convertPickup(std::vector &lines) { std::vector convertedLines; //can ignore angle of pickups in xonotic format std::string coords[3]; int pickupID; std::string trash; bool havePosition = false; bool havePickupID = false; if ( lines.size() < 3 ) { throw std::runtime_error("error: Pickup 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: Pickup entity requires coordinates"); } havePosition = true; } else if ( type == "pickupType" ) { std::istringstream iss(lines[i]); // UInt8 pickupType ID if ( ! (iss >> trash >> trash >> pickupID) ) { throw std::runtime_error("error: Pickup entity requires Pickup ID"); } havePickupID = true; } } if ( havePosition && havePickupID ) { std::stringstream oss; oss << "\"classname\" \"" << pickupMapping_[pickupID] << "\"" << std::endl; convertedLines.push_back ( oss.str() ); // coordinates reordered to x, z, y std::stringstream oss2; oss2 << "\"origin\" \"" << coords[0] << " " << coords[2] << " " << coords[1] << "\"" << std::endl; convertedLines.push_back ( oss2.str() ); return convertedLines; } else { throw std::runtime_error("error: Pickup requires position and pickup ID, missing 1 or both"); } } /* *-------------------------------------------------------------------------------------- * Class: EntityConverter * Method: EntityConverter :: convertPlayerSpawn * Notes: REFLEX * -Optionally includes angle, team indicator, and 0 or more game * mode indicators (defaults to all modes enabled so this line * is used to disable a mode. eg Bool8 modeRace 0) *-------------------------------------------------------------------------------------- */ std::vector EntityConverter::convertPlayerSpawn(std::vector &lines) { std::vector convertedLines; //Requires position coordinate std::string coords[3]; //Requires an angle so if no reflex one is given, use 0 std::string angle("0"); // 3 for race spawn, 1-2 for corresponding team, 0 for deathmatch spawn int team = 0; std::string trash; bool havePosition = false; if ( lines.size() < 2 ) { throw std::runtime_error("error: PlayerSpawn entity requires at least 2 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: PlayerSpawn entity requires position coordinates"); } havePosition = true; } else if ( type == "angles" ) { std::istringstream iss(lines[i]); // UInt8 pickupType ID if ( ! (iss >> trash >> trash >> angle ) { throw std::runtime_error("error: Pickup entity requires Pickup ID"); } } else if ( type == "teamA" ) { team = 2; // Bool8 teamA 0 indicates teamB only } else ef ( type == "teamB" ) { team = 1; // Bool8 teamB 0 indicates teamA only } } if ( havePosition ) { switch (team) { case 0: convertedLines.push_back ( "\"classname\" \"info_player_deathmatch\"" ); break; case 1: convertedLines.push_back ( "\"classname\" \"info_player_team1\"" ); break; case 2: convertedLines.push_back ( "\"classname\" \"info_player_team2\"" ); break; } std::stringstream oss; // coordinates reordered to x, z, y oss << "\"origin\" \"" << coords[0] << " " << coords[2] << " " << coords[1] << "\"" << std::endl; convertedLines.push_back ( oss.str() ); std::stringstream oss2; oss2 << "\"angle\" \"" << angle << "\"" << endl; convertedLines.push_back ( oss2.str() ); return convertedLines; } else { throw std::runtime_error("error: PlayerSpawn entity requires position coordinates"); } } std::vector EntityConverter::convertJumpPad(std::vector &lines) { std::vector convertedLines; std::string targetName; std::string trash; if ( lines.size() < 2 ) { throw std::runtime_error("error: JumpPad entity requires at least 2 lines"); } std::istringstream iss(lines[1]); // String32 target targetName if ( ! (iss >> trash >> trash >> targetName) ) { throw std::runtime_error("error: JumpPad entity requires target name"); } convertedLines.push_back ( "\"classname\" \"trigger_push\"\n" ); std::stringstream oss; oss << "\"target\" \"" << targetName << "\"" << std::endl; convertedLines.push_back ( oss.str() ); return convertedLines; } std::vector EntityConverter::convertTeleporter(std::vector &lines) { std::vector convertedLines; std::string targetName; std::string trash; if ( lines.size() < 2 ) { throw std::runtime_error("error: Teleport entity requires at least 2 lines"); } std::istringstream iss(lines[1]); // String32 target targetName if ( ! (iss >> trash >> trash >> targetName) ) { throw std::runtime_error( "error: Teleport entity requires target name" ); } convertedLines.push_back ( "\"classname\" \"trigger_teleport\"\n" ); std::stringstream oss; oss << "\"target\" \"" << targetName << "\"" << std::endl; convertedLines.push_back ( oss.str() ); return convertedLines; } std::vector EntityConverter::convertTarget(std::vector &lines) { std::vector convertedLines; //position and name required, angles optional std::string coords[3]; std::string targetName; std::string angle; std::string trash; bool havePosition = false; bool haveName = false; bool haveAngle = false; if ( lines.size() < 3 ) { throw std::runtime_error("error: Target 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: Target entity requires coordinates" ); } havePosition = true; } else if ( type == "name" ) { std::istringstream iss(lines[i]); // UInt8 name uniqueName if ( ! (iss >> trash >> trash >> targetName) ) { throw std::runtime_error( "error: Target entity requires target name" ); } haveName = true; } else if ( type == "angles" ) { std::istringstream iss(lines[i]); // Vector3 angles angle notapplicable notapplicable if ( ! (iss >> trash >> trash >> angle) ) { throw std::runtime_error( "error: Target entity requires target angle if specified" ); } haveAngle = true; } } if ( havePosition && haveName) { //**! no way to tell if teleporter or jump pad dest from targetName alone if ( targetMap_[targetName] == "Teleporter") { convertedLines.push_back ( "\"classname\" \"misc_teleporter_dest\"\n" ); } else if ( targetMap_[targetName] == "JumpPad") { convertedLines.push_back ( "\"classname\" \"target_push\"\n" ); } std::stringstream oss; oss << "\"targetname\" \"" << targetName << "\"\n"; convertedLines.push_back ( oss.str() ); // coordinates reordered to x, z, y std::stringstream oss2; oss2 << "\"origin\" \"" << coords[0] << " " << coords[2] << " " << coords[1] << "\"" << std::endl; convertedLines.push_back ( oss2.str() ); // Write angle only if position and name exist if ( haveAngle ) { std::stringstream oss3; oss3 << "\"angle\" \"" << angle << "\"\n"; convertedLines.push_back (oss3.str() ); } } return convertedLines; } std::vector EntityConverter::convertRaceStart(std::vector &lines) { std::vector convertedLines; convertedLines.push_back ("\"classname\" \"trigger_race_checkpoint\""); convertedLines.push_back ("\"cnt\" \"1\""); return convertedLines; } std::vector EntityConverter::convertRaceFinish(std::vector &lines) { std::vector convertedLines; convertedLines.push_back ("\"classname\" \"trigger_race_checkpoint\""); convertedLines.push_back ("\"cnt\" \"0\""); return convertedLines; } // DEBUG void EntityConverter::printMapping() { std::map::iterator it; for (it=pickupMapping_.begin(); it!=pickupMapping_.end(); ++it) std::cout << it->first << " => " << it->second << std::endl; } // DEBUG void EntityConverter::printTargetSources() { std::map::iterator it; for (it=targetMap_.begin(); it!=targetMap_.end(); ++it) std::cout << it->first << " => " << it->second << std::endl; }