From 225e1375efd3444a6c37826fc675a58496b39f8e Mon Sep 17 00:00:00 2001 From: suhrke Date: Sun, 2 Jul 2017 20:22:09 -0700 Subject: [PATCH] broke apart EntityConverter::convert, added submodule --- .gitmodules | 3 + ReflexToQ3/.gitignore | 2 +- ReflexToQ3/Makefile | 4 +- ReflexToQ3/includes/EntityConverter.cpp | 516 ++++++++++++++---------- ReflexToQ3/includes/EntityConverter.hpp | 93 ++++- ReflexToQ3/includes/cxxopts | 1 + ReflexToQ3/main.cpp | 2 +- 7 files changed, 410 insertions(+), 211 deletions(-) create mode 100644 .gitmodules create mode 160000 ReflexToQ3/includes/cxxopts diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..82fc687 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ReflexToQ3/includes/cxxopts"] + path = ReflexToQ3/includes/cxxopts + url = https://github.com/jarro2783/cxxopts.git diff --git a/ReflexToQ3/.gitignore b/ReflexToQ3/.gitignore index d8171e6..2c0ed78 100644 --- a/ReflexToQ3/.gitignore +++ b/ReflexToQ3/.gitignore @@ -6,4 +6,4 @@ *.log # executables -reflex2q3_* +reflex2q3 diff --git a/ReflexToQ3/Makefile b/ReflexToQ3/Makefile index 41047a7..8c016ed 100644 --- a/ReflexToQ3/Makefile +++ b/ReflexToQ3/Makefile @@ -1,9 +1,9 @@ EX=reflex2q3 CC=g++ -CFLAGS=-std=c++11 -I"includes" -I"/usr/include/eigen3" +CFLAGS=-std=c++11 -I"./includes" -I"./includes/cxxopts/include" -I"/usr/include/eigen3" TESTEX=test/test-parser -all: main test +all: main 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 7847fc4..d0c1f64 100644 --- a/ReflexToQ3/includes/EntityConverter.cpp +++ b/ReflexToQ3/includes/EntityConverter.cpp @@ -24,44 +24,36 @@ + +/*----------------------------------------------------------------------------- + * 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) { - //Open .ent mapping file - std::ifstream entFin; - entFin.open(entityMapFile); - - if ( entFin.is_open() ) { - //Read .ent contents into pickup map - std::string line; - while (std::getline(entFin, 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" ); - } - entFin.close(); + mapEntities(entityMapFile); - - //Open .map file - std::ifstream mapFin; - mapFin.open(reflexMapFile); + // Pre-scan for related entities + std::ifstream fin; + fin.open(reflexMapFile); - if ( mapFin.is_open() ) { + 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(mapFin, line)) { + while (std::getline(fin, line)) { if ( line.find("type Teleporter") != std::string::npos) { - std::getline(mapFin, line); + std::getline(fin, line); std::istringstream iss(line); if ( ! (iss >> trash >> trash >> targetName)) { throw std::runtime_error( "format error in .map file"); @@ -69,7 +61,7 @@ EntityConverter::EntityConverter(std::string entityMapFile, std::string reflexMa targetMap_.insert ( std::pair(targetName, "Teleporter") ); } else if ( line.find("type JumpPad") != std::string::npos) { - std::getline(mapFin, line); + std::getline(fin, line); std::istringstream iss(line); if ( ! (iss >> trash >> trash >> targetName)) { throw std::runtime_error( "format error in .map file"); @@ -81,7 +73,8 @@ EntityConverter::EntityConverter(std::string entityMapFile, std::string reflexMa else { throw std::ios::failure( "Error: EntityConverter failed to open .map file" ); } - mapFin.close(); + fin.close(); + areEntitiesMatched_ = true; //DEBUG //printMapping(); @@ -89,24 +82,61 @@ EntityConverter::EntityConverter(std::string entityMapFile, std::string reflexMa } -// 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::vector +EntityConverter::convert(std::vector lines) { - std::map::iterator it; - for (it=targetMap_.begin(); it!=targetMap_.end(); ++it) - std::cout << it->first << " => " << it->second << std::endl; + 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" ); + } } -std::string EntityConverter::getAttributeType(std::string line) + +/*----------------------------------------------------------------------------- + * PRIVATE + *-----------------------------------------------------------------------------*/ + +std::string +EntityConverter::getAttributeType(std::string line) { std::string type; std::string dataType; @@ -120,186 +150,260 @@ std::string EntityConverter::getAttributeType(std::string line) -std::vector EntityConverter::convert(std::vector lines) +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; std::string coords[3]; - std::string attribute; - std::string trash; //unused tokens + std::string trash; - std::string type; - if ( lines.size() < 1 ) { - std::cerr << "error: empty entity cannot be converted" << std::endl; + 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; } - // second token is the type - std::istringstream iss(lines[0]); - if ( ! (iss >> trash >> type)) { - std::cerr << "error: type is required" << std::endl; - return convertedLines; + else { + throw std::runtime_error("error: Pickup requires position and pickup ID, missing 1 or both"); } +} - if ( type == "Pickup" ) { ////PICKUP - if ( lines.size() < 3 ) { - std::cerr << "error: Pickup entity requires at least 3 lines" << std::endl; - return convertedLines; - } - //can ignore angle of pickups in xonotic format - int pickupID; - bool havePosition = false; - bool havePickupID = false; - for (int i = 1; i < lines.size(); i++) { - type = getAttributeType(lines[i]); - if ( type == "position" ) { - std::istringstream iss2(lines[i]); - // Vector3 position coord0 coord1 coord2 - if ( ! (iss2 >> trash >> trash >> - coords[0] >> coords[1] >> coords[2])) { - std::cerr << "error: Pickup entity requires coordinates" << std::endl; - return convertedLines; - } - havePosition = true; - } - else if ( type == "pickupType" ) { - std::istringstream iss2(lines[i]); - // UInt8 pickupType ID - if ( ! (iss2 >> trash >> trash >> pickupID) ) { - std::cerr << "error: Pickup entity requires pickup ID" << std::endl; - return convertedLines; - } - 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() ); - } - } - else if ( type == "PlayerSpawn" ) { ///PLAYER SPAWN - //minimum of 3 lines, max of ? lines - std::istringstream iss2(lines[1]); - } - else if ( type == "JumpPad" ) { ///JUMP PAD - if ( lines.size() < 2 ) { - std::cerr << "error: JumpPad entity requires at least 2 lines" << std::endl; - } - std::istringstream iss2(lines[1]); - std::string targetName; - // String32 target targetName - if ( ! (iss2 >> trash >> trash >> targetName) ) { - std::cerr << "error: JumpPad entity requires target name" << std::endl; - } - - convertedLines.push_back ( "\"classname\" \"trigger_push\"\n" ); - std::stringstream oss; - oss << "\"target\" \"" << targetName << "\"" << std::endl; - convertedLines.push_back ( oss.str() ); + +std::vector +EntityConverter::convertPlayerSpawn(std::vector &lines) +{ + //minimum of 3 lines, max of ? lines + +} + + + +std::vector +EntityConverter::convertJumpPad(std::vector &lines) +{ + std::vector convertedLines; + std::string trash; + + if ( lines.size() < 2 ) { + throw std::runtime_error("error: JumpPad entity requires at least 2 lines"); } - else if ( type == "Teleporter" ) { ///TELEPORTER - if ( lines.size() < 2 ) { - throw std::runtime_error("error: Teleport entity requires at least 2 lines"); - } - std::istringstream iss2(lines[1]); - std::string targetName; - // String32 target targetName - if ( ! (iss2 >> 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() ); - } - else if ( type == "Target" ) { ///TARGET - 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; + 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; +} - for (int i = 1; i < lines.size(); i++) { - type = getAttributeType(lines[i]); - if ( type == "position" ) { - std::istringstream iss2(lines[i]); - // Vector3 position coord0 coord1 coord2 - if ( ! (iss2 >> trash >> trash >> - coords[0] >> coords[1] >> coords[2])) { - throw std::runtime_error( "error: Target entity requires coordinates" ); - } - havePosition = true; + + +std::vector +EntityConverter::convertTeleporter(std::vector &lines) +{ + std::vector 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 +EntityConverter::convertTarget(std::vector &lines) +{ + std::vector 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" ); } - else if ( type == "name" ) { - std::istringstream iss2(lines[i]); - // UInt8 name uniqueName - if ( ! (iss2 >> trash >> trash >> targetName) ) { - throw std::runtime_error( "error: Target entity requires target name" ); - } - haveName = true; + 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" ); } - 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; + 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" ); } - - 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() ); - } + 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() ); } - } - else if ( type == "Effect" ) { ///EFFECT - // to be implemented - } - else if ( type == "PointLight" ) { ////POINT LIGHT - // to be implemented - } - else if ( type == "Prefab" ) { ////PREFAB - // to be implemented? - } - else if ( type == "CameraPath" ) { ///CAMERA PATH - // to be implemented? - } - else if ( type == "WorldSpawn" ) { ///WORLD SPAWN - // do nothing } return convertedLines; } + + + +std::vector +EntityConverter::convertRaceStart(std::vector &lines) +{ + std::vector convertedLines; + + return convertedLines; +} + + + +std::vector +EntityConverter::convertRaceFinish(std::vector &lines) +{ + std::vector convertedLines; + + 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; +} + diff --git a/ReflexToQ3/includes/EntityConverter.hpp b/ReflexToQ3/includes/EntityConverter.hpp index 239d654..40fe0ef 100644 --- a/ReflexToQ3/includes/EntityConverter.hpp +++ b/ReflexToQ3/includes/EntityConverter.hpp @@ -4,6 +4,13 @@ * Filename: EntityConverter.hpp * * Description: Convert reflex entities to xonotic entities + * - Simple; operates on single entity at a time + * - Only context provided is information on what entities are related. + * (i.e. a teleport and it's destination) Can get this information + * through the pre-scan constructor of the .map file or by providing + * a queue of all the entities in the file. + * - Throws exceptions upon encountering malformed entities and when + * IO errors occur during object instantiation * * Version: 1.0 * Created: 05/27/2017 08:21:14 AM @@ -19,6 +26,8 @@ #define ENTITY_CONVERTER_HPP // Reflex Format +// -****While Worldspawn is an entity, an external parser handles +// this because it contains all other entities // -"Pickup" denoted by ID // conventional item and weapon conversion stored in r2x.ent // -"PlayerSpawn" consists of coordinate (Vector3), @@ -30,7 +39,9 @@ // -"Target" stored as a position (Vector3) and // "name" (String32) // ***Target can be destination of teleports OR jump pads -// -MORE TO BE ADDED (Effect, liquids etc) +// -"RaceStart" stored as a brush +// -"RaceFinish" stored as a brush +// -MORE TO BE ADDED? (Effect, PointLight, Prefab, CameraPath, liquids) // Xonotic Format // -pickups prefixed by either "item_" or "weapon_" @@ -55,6 +66,8 @@ // a coordinate "origin" (vector3), // an angle "angle" (a single number), // a target "targetname" +// -checkpoints stored as "trigger_race_checkpoint", +// a count "cnt", where "cnt" "0" is the finish line (start line?) #include #include @@ -63,14 +76,92 @@ class EntityConverter { public: + /* + *-------------------------------------------------------------------------------------- + * Class: EntityConverter + * Method: Constructor + * Description: Creates entity format mapping + * CAUTION: Requires matchRelated 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); + /* + *-------------------------------------------------------------------------------------- + * Class: EntityConverter + * Method: Constructor + * Description: Creates entity format mapping and pre-scans for related entities + * 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); + /* + *-------------------------------------------------------------------------------------- + * Class: EntityConverter + * Method: EntityConverter :: convert + * Description: Converts a single entity from reflex to xonotic format + * Parameter: vector of strings "lines", lines that comprise a single entity + * Return: vector of strings, single entity in the converted format + * THROWS: runtime_error on malformed .map file + * THROWS: runtime_error when called before related entitios are matched + *-------------------------------------------------------------------------------------- + */ std::vector convert(std::vector lines); + + + protected: + + + private: + /* + *-------------------------------------------------------------------------------------- + * Class: EntityConverter + * Method: EntityConverter :: getAttributeType + * Description: Extracts the type from a line + * Parameter: string "line", entity keyword followed by the type + *-------------------------------------------------------------------------------------- + */ std::string getAttributeType(std::string line); + /* + *-------------------------------------------------------------------------------------- + * Class: EntityConverter + * Method: EntityConverter :: mapEntities + * Description: Prepare pickupMapping_ + * Parameter: string mapFile, filename of pickup mapping + * Return: true if no error, false if error + *-------------------------------------------------------------------------------------- + */ + void mapEntities(std::string mapFile); + + /* + *-------------------------------------------------------------------------------------- + * 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 + * 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); + void printMapping(); //DEBUG void printTargetSources(); //DEBUG + // 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 diff --git a/ReflexToQ3/includes/cxxopts b/ReflexToQ3/includes/cxxopts new file mode 160000 index 0000000..11faade --- /dev/null +++ b/ReflexToQ3/includes/cxxopts @@ -0,0 +1 @@ +Subproject commit 11faadeba77d05a80c751e97142875c4b296fa87 diff --git a/ReflexToQ3/main.cpp b/ReflexToQ3/main.cpp index 7bf7284..a0ae841 100644 --- a/ReflexToQ3/main.cpp +++ b/ReflexToQ3/main.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include "cxxopts.hpp" #include "oopless-parser.hpp" #include "brushdef.hpp"