Note: It's recommended you read the master branch version of this document to make sure all the information is up to date.
We've moved to using GitHub Issues for issue tracking. Feel free to take a look and assign yourself if you're interested in working on something, or report bugs/features not yet included.
Also come join the Discord channel to discuss the project with others (see the "nt_semantic_sequels" and other related channels under the channel group "Architects").
The overall goal is to create a reimplementation of Neotokyo for Source 2013 MP engine, with the source code opened for viewing and editing (with certain limitations; see the Source SDK license for legalese). Original NT assets/IP are mounted as dependencies, and aren't part of the repository. It's like a mod of a mod.
The end result should hopefully be a shinier and less error-prone rendition of NT, with the source code and more cvars providing room to fine tune game balance, or come up with completely new modes entirely.
To see the Table of Contents, please use the "Outline" feature on GitHub by clicking the button located in the top right of this document.
It's recommended you fork the master branch, and pull request your work there back to it.
See README.md in this repo for setting up your build environment (currently supporting Windows/Linux).
To be safe and avoid problems with VAC, it's recommended to add a -insecure launch flag before attaching your debugger.
In the CMake Target View, right-click "client (shared library)" and click on "Add Debug Configuration". This should generate the src/.vs/launch.vs.json config file.
Modify the config to fix the directory paths; an example is provided below:
{
"version": "0.2.1",
"defaults": {},
"configurations": [
{
"type": "dll",
"exe": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Source SDK Base 2013 Multiplayer\\hl2_win64.exe",
"project": "CMakeLists.txt",
"projectTarget": "client.dll (game\\client\\client.dll)",
"name": "client.dll (game\\client\\client.dll)",
"currentDir": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Source SDK Base 2013 Multiplayer",
"args": [
"-allowdebug",
"-insecure",
"-dev",
"-sw",
"-game",
"C:\\PATH\\TO\\REPO_ROOT\\neo\\game\\neo"
]
}
]
}
On the sidebar, click "Projects" then under your current kit, click "Run". Set the following:
| Property | Example value |
|---|---|
| Executable | ~/.steam/steam/steamapps/common/Source SDK Base 2013 Multiplayer/hl2_linux64 |
| Command line arguments | -allowdebug -insecure -dev -sw -game "/PATH/TO/NEO_REPO/game/neo" |
| Working directory | ~/.steam/steam/steamapps/common/Source SDK Base 2013 Multiplayer |
Then under Environment, expand by clicking "Details" and add in:
LD_LIBRARY_PATH=[INSERT_OUTPUT_HERE]:/home/YOUR_USER/.steam/steam/steamapps/common/Source SDK Base 2013 Multiplayer/bin/linux64
SDL_VIDEODRIVER=x11
SteamEnv=1
Next is finding the 64-bits steam-runtime under the Steam installation. This can be found
using the following command, replacing $HOME if Steam is installed at another directory:
$ find "$HOME" -type d -name 'steam-runtime' 2> /dev/null
Assuming default directory, it might be in either: ~/.steam/steam/steamapps/common/SteamLinuxRuntime/var/steam-runtime or if using a Debian based distribution: [TODO].
Then change to that directory and replace [INSERT_OUTPUT_HERE] to the output of:
$ cd <STEAM-RUNTIME-DIR>
$ ./run.sh printenv LD_LIBRARY_PATH
After this, you should be able to run and debug NT;RE, just make sure to have Steam open in the background.
Example settings for debugging from Visual Studio solutions:
| Configuration Properties->Debugging | Example value |
|---|---|
| Command | C:\Program Files (x86)\Steam\steamapps\common\Source SDK Base 2013 Multiplayer\hl2_win64.exe |
| Command Arguments | -allowdebug -insecure -dev -sw -game "C:\git\neo\game\neo" |
| Working Directory | C:\Program Files (x86)\Steam\steamapps\common\Source SDK Base 2013 Multiplayer |
Break pointing and stepping the method CServerGameDLL::GameFrame, or relevant methods in C_NEO_Player (clientside player) / CNEO_Player (serverside player) can help with figuring out the general game loop. Neo specific files usually live in game/client/neo, game/server/neo, or game/shared/neo, similar to how most hl2mp code is laid out.
Ochii's impressive reverse engineering project can also serve as reference for figuring things out. However, please refrain from copying reversed instructions line by line, as the plan is to write an open(ed) source (wherever applicable, note the Source SDK license) reimplementation, and steer clear of any potential copyright issues. Same thing applies for original NT assets; you can depend on the original NT installation (it's mounted to the engine filesystem by gameinfo.txt |appid_244630|), but avoid pushing those assets in the repo.
This project uses the CMake build system to generate ninja/makefiles and is integrated with IDEs such as VS2022 and Qt Creator. When modifying the project file structure, look into CMakeLists.txt and cmake/*.cmake.
In shared code, clientside code can be differentiated with CLIENT_DLL, vs. serverside's GAME_DLL. In more general engine files, Neotokyo specific code can be marked with a NEO ifdef.
Only the x86-64 architecture is supported.
- MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)
- MSVC version used by the latest
windows-2025runner image (Microsoft.VisualStudio.Component.VC.Tools.x86.x64)
- GCC 10 steamrt3 'sniper'
- GCC 14 steamrt3 'sniper'
- GCC 14 steamrt4
- Clang 19 steamrt4
- C++20, within the supported compilers' capabilities.
- Formatting:
- No big restrictions on general format, but try to more or less match the surrounding SDK code style for consistency. For example this typically means using tabs for whitespace, but generally go with what the file you're editing is doing stylistically.
- Warnings are treated as errors.
- You may choose to suppress a warning with compiler-specific preprocessing directives if it is a false positive, but please do not suppress valid warnings; instead resolve it by fixing the underlying issue.
- For local development, you may disable warnings-as-errors by modifying your
CMAKE_COMPILE_WARNING_AS_ERRORoption, eg. by modifying the entry in yourCMakeCache.txtfile. - For the CI runners, the following cases are exempt from the warnings-as-errors rule:
- Tagged release builds (
v<major>.<minor>...) - Branch name or its latest commit message containing the phrase
nwae(acronym for: "no warnings as errors")- The
nwaephrase also works locally, but you may need to refresh the CMake cache for it to take effect.
- The
- Tagged release builds (
- Please note that the above methods of disabling warnings-as-errors are intended as temporary workarounds to allow devs to resolve exceptional situations (eg. the un-pinned MSVC compiler version changes unexpectedly changing warnings behaviour), and it should not be regularly relied upon to dodge warnings. The preferred long-term solution is almost always to properly fix the warning!
- STL and platform-specific headers may be used when needed, but generally the SDK libraries should be preferred. If you do use such includes, be careful with not bringing in conflicting macro definitions or performance-costly code.
- The
minandmaxfrom the SDK macros have been disabled, because they were polluting namespaces and making things like like::maxawkward to use. You may use the templatedMinandMaxfunctions frombasetypes.hinstead. - Some nonstandard casting helpers are available:
assert_cast:- The preferred cast for pointer conversion where an incompatible cast may occur by accident
- If you know for a fact it is always a safe cast, you can use
static_castinstead
- If you know for a fact it is always a safe cast, you can use
- For debug builds, will runtime-validate the cast with
ptr == dynamic_cast<T>(ptr) - For release builds, it is identical to
static_cast
- The preferred cast for pointer conversion where an incompatible cast may occur by accident
narrow_cast:- The preferred cast for narrowing conversions where loss of information may occur
- Use to mark narrowing conversions where the input could overflow
- If you know for a fact it is always a safe cast, you can use
static_castinstead
- For debug builds, will runtime-validate narrowing conversions for:
- Roundtrip equality:
value v of type A equals itself after A->B->A type conversion - Signed/unsigned conversion overflow
- Roundtrip equality:
- For release builds, it is identical to
static_cast
- The preferred cast for narrowing conversions where loss of information may occur
neo::bit_cast:- The preferred bit cast function, for performing a well-formed type pun
- Mostly used to avoid strict aliasing rule violations in the SDK code
- Acts as a wrapper for
std::bit_castwhen it's available, else will fall back tomemcpybased conversions (for example on the steamrt3 default GCC compiler) - For debug builds, a runtime assertion test is available as an additional parameter:
auto output = neo::bit_cast<T>(BC_TEST(input, expectedOutput));- When replacing ill-formed type puns, this test syntax can be used to ensure the output of
neo::bit_cast<T>(input)remains identical toexpectedOutput
- For release builds, the above test optimizes away, into:
auto output = neo::bit_cast<T>(input);
- Valve likes to
( space )indent their parentheses, especially with macros, but it's not necessary to strictly follow everywhere. - Tabs are preferred for indentation, to be consistent with the SDK code.
- When using a TODO/FIXME/HACK... style comment, use the format
// NEO TODO (Your-username): Example comment.to make it easier to search forNEOspecific todos/fixmes (opposed to Valve ones of the original SDK), and at a glance figure out who has written them. - For classes running on both client and server, you should generally follow Valve's C_Thing (client) -- CThing (server) convention. On shared files, this might mean #defining serverclass for client, or vice versa. There's plenty of examples of this pattern in Valve's classes for reference, for example here.