OpenEnroth 2a41f3e
|
This document describes the development process we're following. It's required reading for anyone intending to contribute.
Main dependencies:
By default, we are using prebuilt dependencies, and they are resolved automatically during the cmake phase.
The only exception is Linux, where we're using SDL2 from the distribution, so you will need to install a development versions of SDL2 before building OpenEnroth. E.g. on Ubuntu:
Additional dependencies:
Minimum required compiler versions are as follows:
The following IDEs have been tested and should work fine:
This project uses the CMake build system. Use the following commands to clone the repository and build OpenEnroth:
To cross-compile for 32-bit x86, you can pass -m32
via compiler flags to cmake. In the snipped above that would mean running export CFLAGS="-m32" CXXFLAGS="-m32"
first.
You can also select platform dependent generator for your favorite IDE.
https://git-scm.com/download/win
) and Visual Studio 2022.https://github.com/OpenEnroth/OpenEnroth
.c:\Program Files\Microsoft Visual Studio\2022\<edition>\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin
).OpenEnroth.exe
.If you wish you can also disable prebuilt dependencies by turning off OE_USE_PREBUILT_DEPENDENCIES
cmake option and pass your own dependencies source, e.g. via vcpkg integration.
Be aware that Visual Studio has a bug with git submodules not syncing between branches. So when checking out the branch or switching to different branch you may need to run the following command manually: git submodule update --init
For the C++ code we are following the Google C++ Style Guide. Source code is automatically checked against it and pull request will fail if you don't follow it.
To perform a style check before pushing anything you can build check_style
target. In Visual Studio you can do that by going to Solution Explorer → Change Views → CMake targets. Right click and build check_style
, errors will be listed in output.
We also follow some additional style preferences, as listed below.
Documentation:
@
used for tags, and starting with /**
comment introducer.@par Original binary offset:\ilinebr
doxygen tag (e.g. @par Original binary offset:\ilinebr 0xCAFEBEEF
).Naming:
MM_
prefix for macro naming. Macro names should be in SNAKE_CASE_ALL_CAPS
.SNAKE_CASE_ALL_CAPS
for enum
values. E.g. ITEM_CRATE_OF_ARROWHEADS
, ITEM_SLOT_RING6
.MONSTER_TROLL_A
for a value of enum class MonsterId
. For values that are then used in array bounds, put the _FIRST
, _LAST
and _COUNT
right after the name of the type, e.g. ITEM_FIRST_MESSAGE_SCROLL
.CamelCase
for everything else.IndexedArray
, InputAction
, LogLevel
. This applies to all types, including classes, structs, enums and typedefs, with certain exceptions as listed below.Vec3::length
, gridCellToWorldPosX
, ceilingHeight
.int monsterCount = level->monsterCount()
.this->
every single time. E.g. _initialized = true
, where _initialized
is a member field.value_type
for iterator value type, and push_back
for a method that's adding an element to a container.Code formatting:
*
and &
in type declarations should be preceded by a space. So it's char *string
, and not char* string
.Language features:
using Alias = Type
instead of typedef Type Alias
.enum class
es followed by using enum
statements instead of ordinary enum
s. This provides type safety without changing the syntax. For flags, use Flags
class.enum
s if you really need to have implicit casts to integer types, but this is a very rare use case. If you're using enum
values to index into some array, consider using enum class
coupled with IndexedArray
.std::unique_ptr
by value. If it allocates its result and passes ownership to the caller, then it should return std::unique_ptr
.int
unless you need something else. Don’t try to avoid negative values by using unsigned
, this implies many changes to the usual behavior of integers and can be a source of bugs. See a section in core guidelines for more info. However, don't overdo it, use size_t
for indexing into STL containers.std::string_view
passed by value.std::string
or passing it by const reference, the performance benefits are almost always negligible.TransparentString*
classes if you need to index into a string map using std::string_view
keys.std::string
or const std::string &
parameters where it makes your code simpler (e.g. by avoiding jumping through hoops if you'll need to create an intermediate std::string
object anyway).std::string_view
with a string literal using operator+
, and never will). In most cases you should be fine just using fmt::format
for this. If fmt::format
looks like an overkill, use join
from Utility/String/Transformations.h
.ns1::Context
and ns2::Context
, and when coupled with a bit of using
here and there this makes the code harder to read and reason about. Please spend some time coming up with good names for your classes instead.namespace lod
.namespace detail
that you're encouraged to use to hide implementation details and to prevent cluttering of the global namespace.std::string
s in the code are assumed to be UTF8-encoded as advised by utf8everywhere. On Windows this is ensured by the UnicodeCrt
class that sets up the standard library to use UTF8 encoding. Thus, you don't need to bother with std::u8string
, and you can safely convert std::filesystem::path
to string by calling path.string()
.Error handling:
assert
s to check for coding errors and conditions that must never be false, no matter how the program is run.class Exception
.Logger
for warnings and recoverable errors.There is a lot of code in the project that doesn't follow these conventions. Please feel free to fix it, preferably not mixing up style and logical changes in the same PR.
OpenEnroth code is broken up as follows:
thirdparty
– this is where all external libraries go.Utility
– generic utility classes and functions go here. Utility classes should be domain-independent (e.g. should make sense in a context of some other project) and should depend only on thirdparty
libraries.Library
– collection of independent libraries that the engine is built on top of. Code here can depend on Utility
and other libraries in Library
. However, there should be no cyclical dependencies between libraries here.Library/Platform
is our platform abstraction layer on top of SDL.Our basic guidelines for code organization are:
CMakeLists.txt
file per folder. Exceptions are /android, /CMakeModules and /resources.We strive for a good test coverage of the project, and while we're not there yet, the current policy is to add tests for all the bugs we fix, as long as the fix is testable. E.g. graphical glitches are generally very hard to test, but we have the infrastructure to test game logic and small isolated classes.
Tests in OpenEnroth fall into two categories:
src/Utility/Tests
.Game tests work by instrumenting the engine, and then running test code in between the game frames. This code usually sends events to the engine (e.g. mouse clicks), which are then processed by the engine in the next frame, but it can do pretty much anything else – all of engine's data is accessible and writable from inside the game test.
As typing out all the events to send inside the test code can be pretty tedious, we also have an event recording functionality, so that the test creation workflow usually looks as follows:
Ctrl+Shift+R
to start recording an event trace. Check logs to make sure that trace recording has started.Ctrl+Shift+R
again to stop trace recording. You will get two files generated in the current folder – trace.json
and trace.mm7
.issue_XXX.json
and issue_XXX.mm7
) and create a PR to the OpenEnroth_TestData repo.TestController::playTraceFromTestData
to play back your trace, and add the necessary checks around it.If you need to record a trace with non-standard FPS (e.g. if an issue doesn't reproduce on lower FPS values), set debug.trace_frame_time_ms
config value before starting OpenEnroth for recording.
To run all unit tests locally, build a UnitTest
cmake target, or build & run OpenEnroth_UnitTest
.
To run all game tests locally, set OPENENROTH_MM7_PATH
environment variable to point to the location of the game assets, then build Run_GameTest_Headless_Parallel
cmake target. Alternatively, you can build OpenEnroth_GameTest
, and run it manually, passing the paths to both game assets and the test data via command line.
If you need to look closely at the recorded trace, you can play it by running OpenEnroth play --speed 0.5 <path-to-trace.json>
. Alternatively, if you already have a unit test that runs the recorded trace, you can run OpenEnroth_GameTest --speed 0.5 --gtest_filter=<test-suite-name>.<test-name> --test-path <path-to-test-data-folder>
. Note that --gtest_filter
needs that =
and won't work if you try passing test name after a space.
Changing game logic might result in failures in game tests because they check random number generator state after each frame, and this will show as Random state desynchronized when playing back trace
message in test logs. This is intentional – we don't want accidental game logic changes. If the change was actually intentional, then you might need to either retrace or re-record the traces for the failing tests. To retrace, run OpenEnroth retrace <path-to-trace.json>
. Note that you can pass multiple trace paths to this command.
We're using Lua as the scripting language, and all our scripts are currently located under the resources/scripts
folder.
Script files undergo a syntax checking process during the build generation. If you intend to work with scripts, it is recommended to install the Lua Language Server to run the style checker locally. Follow these steps to setup LuaLS
locally:
LuaLS
through one of the following methods. Ensure that the lua-language-server executable is available in the PATH
environment variable.check_style
target is now including scripting in its tests.Little note: If LuaLS
is not found, everything still build but no checks will be run against the Lua scripts.
To go through a better experience while working with scripts it is strongly recommended to use VS Code and install the LuaLS extension. Just be sure to open the root repository folder in VS Code
. By doing so LuaLS
reads the correct configuration file used by the project
Scripting is currently used only for debugging purposes. Modding support is not planned for the near future. You can check the milestones to get a better idea.
The game features a console capable of executing various Lua script commands. To activate this feature, please follow these steps:
~
key to open the debug console.Currently, the console is only available while in-game.
Old event decompiler and IDB files can be found here. Feel free to ping zipi#6029
on Discord for more info.