reflex2q3/ReflexToQ3/includes/EntityConverter.cpp

410 lines
11 KiB
C++

/*
* =====================================================================================
*
* 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 <exception>
#include <fstream>
#include <iostream>
#include <sstream>
/*-----------------------------------------------------------------------------
* 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<std::string, std::string>(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<std::string, std::string>(targetName, "JumpPad") );
}
}
}
else {
throw std::ios::failure( "Error: EntityConverter failed to open .map file" );
}
fin.close();
areEntitiesMatched_ = true;
//DEBUG
//printMapping();
//printTargetSources();
}
std::vector<std::string>
EntityConverter::convert(std::vector<std::string> 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<int, std::string>(id, pickup) );
}
}
else {
throw std::ios::failure( "Error: EntityConverter failed to open .ent file" );
}
fin.close();
}
std::vector<std::string>
EntityConverter::convertPickup(std::vector<std::string> &lines)
{
std::vector<std::string> convertedLines;
std::string coords[3];
std::string trash;
if ( lines.size() < 3 ) {
throw std::runtime_error("error: Pickup entity requires at least 3 lines");
}
//can ignore angle of pickups in xonotic format
int pickupID;
bool havePosition = false;
bool havePickupID = false;
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");
}
}
std::vector<std::string>
EntityConverter::convertPlayerSpawn(std::vector<std::string> &lines)
{
//minimum of 3 lines, max of ? lines
}
std::vector<std::string>
EntityConverter::convertJumpPad(std::vector<std::string> &lines)
{
std::vector<std::string> convertedLines;
std::string trash;
if ( lines.size() < 2 ) {
throw std::runtime_error("error: JumpPad entity requires at least 2 lines");
}
std::istringstream iss(lines[1]);
std::string targetName;
// 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<std::string>
EntityConverter::convertTeleporter(std::vector<std::string> &lines)
{
std::vector<std::string> convertedLines;
std::string trash;
if ( lines.size() < 2 ) {
throw std::runtime_error("error: Teleport entity requires at least 2 lines");
}
std::istringstream iss(lines[1]);
std::string targetName;
// 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<std::string>
EntityConverter::convertTarget(std::vector<std::string> &lines)
{
std::vector<std::string> convertedLines;
std::string coords[3];
std::string trash;
if ( lines.size() < 3 ) {
throw std::runtime_error("error: Target entity requires at least 3 lines");
}
//position and name required, angles optional
std::string targetName;
std::string angle;
bool havePosition = false;
bool haveName = false;
bool haveAngle = false;
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 iss2(lines[i]);
// Vector3 angles angle notapplicable notapplicable
if ( ! (iss2 >> 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<std::string>
EntityConverter::convertRaceStart(std::vector<std::string> &lines)
{
std::vector<std::string> convertedLines;
return convertedLines;
}
std::vector<std::string>
EntityConverter::convertRaceFinish(std::vector<std::string> &lines)
{
std::vector<std::string> convertedLines;
return convertedLines;
}
// DEBUG
void
EntityConverter::printMapping()
{
std::map<int, std::string>::iterator it;
for (it=pickupMapping_.begin(); it!=pickupMapping_.end(); ++it)
std::cout << it->first << " => " << it->second << std::endl;
}
// DEBUG
void
EntityConverter::printTargetSources()
{
std::map<std::string, std::string>::iterator it;
for (it=targetMap_.begin(); it!=targetMap_.end(); ++it)
std::cout << it->first << " => " << it->second << std::endl;
}