diff --git a/ReflexToQ3/Makefile b/ReflexToQ3/Makefile index 6b6532f..ae07ccd 100644 --- a/ReflexToQ3/Makefile +++ b/ReflexToQ3/Makefile @@ -1,24 +1,17 @@ EX=reflex2q3 CC=g++ CFLAGS=-std=c++11 -I"./includes" -I"./includes/Catch/single_include" -I"./includes/cxxopts/include" -I"/usr/include/eigen3" -TESTEX=test/test-parser -UNITEX=test/catch +TESTEX=test/catch -all: main unittest +all: main test main: planes.o brushdef.o oopless-parser.o EntityConverter.o $(CC) $^ main.cpp $(CFLAGS) -o $(EX) 2>error8.log -test: planes.o brushdef.o oopless-parser.o test-parser.o +test: EntityConverter.o catch.o $(CC) $^ $(CFLAGS) -o $(TESTEX) - -unittest: EntityConverter.o catch.o - $(CC) $^ $(CFLAGS) -o $(UNITEX) mv test/catch ../.git/hooks/pre-commit -test-parser.o: test/test-parser.cpp - $(CC) -c $^ $(CFLAGS) - catch.o: test/catch.cpp $(CC) -c $^ $(CFLAGS) diff --git a/ReflexToQ3/includes/EntityConverter.cpp b/ReflexToQ3/includes/EntityConverter.cpp index 73e3b16..92190df 100644 --- a/ReflexToQ3/includes/EntityConverter.cpp +++ b/ReflexToQ3/includes/EntityConverter.cpp @@ -249,7 +249,7 @@ EntityConverter::convertPickup(const std::vector &lines) const if ( lines.size() < 2 ) { throw std::runtime_error( - makeErrorMessage( "error: Pickup entity requires at least type and ID", lines )); + makeErrorMessage( "error: Pickup entity requires minimum of two lines (type and ID)", lines )); } for (int i = 1; i < lines.size(); i++) { @@ -260,7 +260,7 @@ EntityConverter::convertPickup(const std::vector &lines) const if ( ! (iss >> trash >> trash >> coords[0] >> coords[1] >> coords[2])) { throw std::runtime_error( - makeErrorMessage( "error: Pickup entity requires coordinates", lines )); + makeErrorMessage( "error: Invalid Pickup position", lines )); } } else if ( type == "pickupType" ) { @@ -268,7 +268,7 @@ EntityConverter::convertPickup(const std::vector &lines) const // UInt8 pickupType ID if ( ! (iss >> trash >> trash >> pickupID) ) { throw std::runtime_error( - makeErrorMessage( "error: Pickup entity requires Pickup ID", lines )); + makeErrorMessage( "error: Format of Pickup ID line is invalid", lines )); } havePickupID = true; } @@ -278,12 +278,12 @@ EntityConverter::convertPickup(const std::vector &lines) const auto pickupIter = pickupMap_.find(pickupID); if ( pickupIter == pickupMap_.end() ) { throw std::runtime_error( - makeErrorMessage( "error: Pickup ID must be valid", lines )); + makeErrorMessage( "error: Pickup ID is invalid", lines )); } std::stringstream pickupStream; pickupStream << "\"classname\" \"" << pickupIter->second << "\"" << std::endl; convertedLines.push_back ( pickupStream.str() ); - // coordinates reordered to x, z, y + // coordinates reordered to x, z, yentity std::stringstream positionStream; positionStream << "\"origin\" \"" << coords[0] << " " << coords[2] << " " << offset(coords[1], OFFSET_PICKUP) << "\"" << std::endl; @@ -292,7 +292,7 @@ EntityConverter::convertPickup(const std::vector &lines) const } else { throw std::runtime_error( - makeErrorMessage( "error: Pickup requires position and pickup ID, missing 1 or both", lines )); + makeErrorMessage( "error: Pickup ID was not in the entity", lines )); } } @@ -335,7 +335,7 @@ EntityConverter::convertPlayerSpawn(const std::vector &lines) const if ( ! (iss >> trash >> trash >> coords[0] >> coords[1] >> coords[2])) { throw std::runtime_error( - makeErrorMessage( "error: PlayerSpawn entity must have valid position coordinates if specified", lines )); + makeErrorMessage( "error: Invalid PlayerSpawn position", lines )); } } else if ( type == "angles" ) { @@ -343,7 +343,7 @@ EntityConverter::convertPlayerSpawn(const std::vector &lines) const // UInt8 pickupType ID if ( ! (iss >> trash >> trash >> angle )) { throw std::runtime_error( - makeErrorMessage( "error: Pickup entity requires Pickup ID", lines )); + makeErrorMessage( "error: Invalid PlayerSpawn angle", lines )); } } // Bool8 modeX 0 indicates this spawn is not for game mode X @@ -414,13 +414,13 @@ EntityConverter::convertJumpPad(const std::vector &lines) const if ( lines.size() < 2 ) { throw std::runtime_error( - makeErrorMessage( "error: JumpPad entity requires at least type and target name", lines )); + makeErrorMessage( "error: JumpPad entity requires minimum of two lines (type and target)", lines )); } std::istringstream iss(lines[1]); // String32 target targetName if ( ! (iss >> trash >> trash >> targetName) ) { throw std::runtime_error( - makeErrorMessage( "error: JumpPad entity requires target name", lines )); + makeErrorMessage( "error: Invalid JumpPad target", lines )); } convertedLines.push_back ( "\"classname\" \"trigger_push\"\n" ); @@ -441,13 +441,13 @@ EntityConverter::convertTeleporter(const std::vector &lines) const if ( lines.size() < 2 ) { throw std::runtime_error( - makeErrorMessage( "error: Teleport entity requires at least type and target name", lines )); + makeErrorMessage( "error: Teleport entity requires minimum of two lines (type and target)", lines )); } std::istringstream iss(lines[1]); // String32 target targetName if ( ! (iss >> trash >> trash >> targetName) ) { throw std::runtime_error( - makeErrorMessage( "error: Teleport entity requires target name", lines )); + makeErrorMessage( "error: Invalid Teleport target", lines )); } convertedLines.push_back ( "\"classname\" \"trigger_teleport\"\n" ); @@ -473,7 +473,7 @@ EntityConverter::convertTarget(const std::vector &lines) const if ( lines.size() < 3 ) { throw std::runtime_error( - makeErrorMessage( "error: Target entity requires at least type and name", lines )); + makeErrorMessage( "error: Target entity requires minimum of two lines (type and name)", lines )); } for (int i = 1; i < lines.size(); i++) { @@ -484,7 +484,7 @@ EntityConverter::convertTarget(const std::vector &lines) const if ( ! (iss >> trash >> trash >> coords[0] >> coords[1] >> coords[2])) { throw std::runtime_error( - makeErrorMessage( "error: Target entity requires coordinates", lines )); + makeErrorMessage( "error: Invalid Target position", lines )); } } else if ( type == "name" ) { @@ -492,7 +492,7 @@ EntityConverter::convertTarget(const std::vector &lines) const // UInt8 name uniqueName if ( ! (iss >> trash >> trash >> targetName) ) { throw std::runtime_error( - makeErrorMessage( "error: Target entity requires target name", lines )); + makeErrorMessage( "error: Invalid Target \"target\"", lines )); } haveName = true; } @@ -501,7 +501,7 @@ EntityConverter::convertTarget(const std::vector &lines) const // Vector3 angles angle notapplicable notapplicable if ( ! (iss >> trash >> trash >> angle) ) { throw std::runtime_error( - makeErrorMessage( "error: Target entity requires target angle if specified", lines )); + makeErrorMessage( "error: Invalid Target angle", lines )); } } @@ -510,7 +510,7 @@ EntityConverter::convertTarget(const std::vector &lines) const if ( haveName) { auto targetIter = targetMap_.find(targetName); if ( targetIter == targetMap_.end() ) { - std::cerr << makeErrorMessage("EntityConverter doesn't know what the source of a Target entity with the following attributes. This entity will not be converted. It is probably an unsupported entity type or feature. (e.g. game over camera)", lines); + std::cerr << makeErrorMessage("End-game camera Target is not a supported feature in id Tech games. Skipping", lines); std::vector empty; return empty; @@ -544,7 +544,7 @@ EntityConverter::convertTarget(const std::vector &lines) const } else { throw std::runtime_error( - makeErrorMessage( "error: Target entity requires position coordinates and targetname", lines )); + makeErrorMessage( "error: \"target\" was not found in this Target entity", lines )); } } @@ -592,7 +592,7 @@ EntityConverter::convertPointLight(const std::vector &lines) const if ( lines.size() < 2 ) { throw std::runtime_error( - makeErrorMessage( "error: PointLight entity requires at least type", lines )); + makeErrorMessage( "error: PointLight entity requires at least one line (type)", lines )); } for (int i = 1; i < lines.size(); i++) { @@ -603,7 +603,7 @@ EntityConverter::convertPointLight(const std::vector &lines) const if ( ! (iss >> trash >> trash >> coords[0] >> coords[1] >> coords[2])) { throw std::runtime_error( - makeErrorMessage( "error: PointLight entity requires valid position coordinates", lines )); + makeErrorMessage( "error: Invalid PointLight position", lines )); } } else if ( type == "intensity" ) { @@ -611,7 +611,7 @@ EntityConverter::convertPointLight(const std::vector &lines) const // Float intensity validFloat if ( ! (iss >> trash >> trash >> intensity) ) { throw std::runtime_error( - makeErrorMessage( "error: PointLight intensity keyword must be followed by a value", lines )); + makeErrorMessage( "error: Invalid PointLight intensity", lines )); } } else if ( type == "color" ) { @@ -619,7 +619,7 @@ EntityConverter::convertPointLight(const std::vector &lines) const // ColourXRGB32 color eightDigitHexValue if ( ! (iss >> trash >> trash >> color) ) { throw std::runtime_error( - makeErrorMessage( "error: PointLight color keyword must be followed by a value", lines )); + makeErrorMessage( "error: Invalid PointLight color", lines )); } haveColor = true; } @@ -792,7 +792,7 @@ EntityConverter::makeErrorMessage(const std::string message, const std::vector entity) const { std::stringstream ss; - ss << std::endl << message << std::endl; + ss << std::endl << message << ":" << std::endl; std::vector::const_iterator it; for ( it=entity.begin(); it!=entity.end(); ++it ) { diff --git a/docs/doc-entities.txt b/docs/doc-entities.txt new file mode 100644 index 0000000..d045652 --- /dev/null +++ b/docs/doc-entities.txt @@ -0,0 +1,176 @@ + +Differences between Reflex and id Tech entities + + Reflex uses a right-handed coordinate system and id Tech uses a left-handed + system so many positions and angles must take this into account when being + converted. + + + Position of Players and Pickups are stored differently in Reflex. Player + positions are stored at a players feet, while in id Tech, at the center of + the character. Converting from Reflex requires the vertical component of + position to be offset by 32 units. Similarly, pickups must be offset by + two units. + + + Entity attributes are often implicit; i.e. if an attribute is required but + unspecified, a default is used + -All game modes are enabled by default and attributes are used to + disable undesired modes or to set 2v2 + -Entities without a position are implicitly at the origin + -PointLights without a color default to white + + + Attributes set in a WorldSpawn are used by all the entities it contains. + -id Tech maps have a maximum of one WorldSpawn. Many are allowed + in Reflex + -WorldSpawns often group PlayerSpawns by game mode + + + Angles + -Can be stored as a Vector3, since rotation about any axis is possible + -Keyword angle is also supported (rotation about the vertical axis) + + + Race Entities + -Unlike id Tech games, is limited by not having checkpoint brushes + -CTS only + + + + +Reflex entity types and all known attributes + + * is used to denote unsupported attributes and entity types + ** For parsing purposes, is handled differently than other entities + + **WorldSpawn + *String32 targetGameOverCamera + *Float timeOfDay + *Float skyAngle + *UInt8 playersMin + *UInt8 playersMax + Bool8 modeFFA + Bool8 modeTDM + Bool8 modeCTF + Bool8 mode1v1 + Bool8 modeRace + Bool8 mode2v2 + + Pickup + (pickupType Required) + Vector3 position + Vector3 angles + UInt8 pickupType - See list of pickup IDs in this document + + PlayerSpawn + Vector3 position + Vector3 angles + Vector3 angle + bool8 teamA + bool8 teamB + Bool8 modeFFA + Bool8 modeTDM + Bool8 modeCTF + Bool8 mode1v1 + Bool8 modeRace + *Bool8 mode2v2 + + JumpPad + String32 target + brush + + Teleporter + String32 target + brush + + + Target + (name Required) + Vector3 position + Vector3 angles + Vector3 angle + String32 name + + RaceStart + brush + + RaceFinish + brush + + PointLight + Vector3 position + ColourXRGB32 color - ARGB, alpha channel always full for PointLights + Float nearAttenuation + Float farAttenuation + + *Prefab + Vector3 position + String64 prefabName + + *CameraPath + (No direct id Tech equivalent) + UInt8 posLerp + Uint8angleLerp + + *Effect + Vector3 position + Vector3 angles + String64 effectName + String256 material0Name + ColourARGB32 material0Albedo + + + + +Pickup IDs (of pickups suitable for conversion) + + Burst Gun (spawn weapon) + This entity will not have a pickupType field. + + Weapons + If the player doesn't have the weapon, then s/he will acquire it. + If it is present, the player gains some ammo respective to the gun. + pickupType 1 = Shotgun + pickupType 2 = Grenade Launcher + pickupType 3 = Plasma Rifle + pickupType 4 = Rocket Launcher + pickupType 5 = Ion Cannon + pickupType 6 = Bolt Rifle + + Ammo + Ammunition for weapons gained on pickup. + pickupType 20 = Burst Gun Ammo + pickupType 21 = Shotgun Ammo + pickupType 22 = Grenade launcher Ammo + pickupType 23 = Plasma Rifle Ammo + pickupType 24 = Rocket Launcher Ammo + pickupType 25 = Ion Cannon Ammo + pickupType 26 = Bolt Rifle Ammo + + Power Up + A temporary buff granted to the player. + pickupType 60 = Carnage (increases a player's outgoing damage) + pickupType 62 = Resist (decreases a player's incoming damage) + + Flags + For use in Capture the Flag + pickupType 70 = Team Alpha Flag (red team) + pickupType 71 = Team Zeta Flag (blue team) + + Health + Restores the player's hitpoints. + pickupType 40 = Small health + pickupType 41 = Medium health + pickupType 42 = large health + pickupType 43 = Mega Health + + Armor + Restores the player's armor. + Reflex has a tiered Armor system. + Green absorbs the least damage, and Red absorbs the most. + pickupType 50 = Armor shard + pickupType 51 = Green Armor + pickupType 52 = Yellow Armor + pickupType 53 = Red Armor + diff --git a/docs/doc-overview.txt b/docs/doc-overview.txt index dac6f1e..be28d9a 100644 --- a/docs/doc-overview.txt +++ b/docs/doc-overview.txt @@ -13,7 +13,7 @@ Features, and Responsibilities of the Program: For Mappers During the conversion process, face related data (texture & color) are preserved. This is both because of the differences between engines and the - numerous games built in idTech engines. Meaning the map converter does not + numerous games built in idTech engines. That is, the map converter does not attempt to retexture the map with generic idTech engine equivalents or per-game equivalents. It is the individual mappers' responsibility to appropriately adapt the map for the target game. Some of the following diff --git a/docs/reflex-mapping.txt b/docs/reflex-mapping.txt deleted file mode 100644 index 2a76ad3..0000000 --- a/docs/reflex-mapping.txt +++ /dev/null @@ -1,31 +0,0 @@ -======================= -=The Reflex Map Format= -======================= - -Worldspawn contains all brushes and entities. The maps I've seen contain only a single Worldspawn. - -"Pickup" denoted by ID number. We stored the Reflex to Xonotic conversion in r2x.pck - - - - -"PlayerSpawn" consists of coordinate (Vector3), - angle (first element of Vector3), - team indicator (on individual lines), - game type indicator (on individual lines) - --"JumpPad" stored as a brush and a Target - --"Teleporter" stored as a brush and a Target - --"Target" stored as a position (Vector3) and - a "name" (String32) - ***Target can be destination of teleports OR jump pads - --"RaceStart" stored as a brush - --"RaceFinish" stored as a brush - -That's all we have converted so far but we intend to convert Effect, -PointLight, Prefab, CameraPath, and possibly liquids. -