diff --git a/Makefile b/Makefile
index cedcf1bd60..da6c6a2ae9 100644
--- a/Makefile
+++ b/Makefile
@@ -22,14 +22,14 @@ install: $(BUILDDIR)
.PHONY: run
run: build
- $(BUILDDIR)/run game
+ cd $(BUILDDIR) && ./run game
.PHONY: test
test: tests checkfast
.PHONY: tests
tests: build
- $(BUILDDIR)/run test -a
+ cd $(BUILDDIR) && ./run test -a
.PHONY: build
build: $(BUILDDIR)
diff --git a/README.md b/README.md
index b3693ce6c8..f40bd91d3f 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
[![openage](/assets/logo/banner.svg)](http://openage.dev)
=========================================================
-**openage**: a volunteer project to create a free engine clone of the *Genie Engine* used by *Age of Empires*, *Age of Empires II (HD)* and *Star Wars: Galactic Battlegrounds*, comparable to projects like [OpenMW](https://openmw.org/), [OpenRA](http://openra.net/), [OpenSAGE](https://github.com/OpenSAGE/OpenSAGE/), [OpenTTD](https://openttd.org/) and [OpenRCT2](https://openrct2.org/). At the moment we focus our efforts on the integration of *Age of Empires II*, while being primarily aimed at POSIX platforms such as **GNU/Linux**.
+**openage**: a volunteer project to create a free engine clone of the *Genie Engine* used by *Age of Empires*, *Age of Empires II (HD)* and *Star Wars: Galactic Battlegrounds*, comparable to projects like [OpenMW](https://openmw.org/), [OpenRA](http://openra.net/), [OpenSAGE](https://github.com/OpenSAGE/OpenSAGE/), [OpenTTD](https://openttd.org/) and [OpenRCT2](https://openrct2.org/).
openage uses the original game assets (such as sounds and graphics), but (for obvious reasons) doesn't ship them.
-To play, you require *an original AoE II: TC, [AoE II: HD](http://store.steampowered.com/app/221380/)* or [AoE II: DE](https://store.steampowered.com/app/813780) installation (via [Wine](https://www.winehq.org/) or [Steam-Linux](doc/media_convert.md)).
+To play, you require *[any of the original games (AoE1, AoE2)](/doc/media_convert.md)* or their *Definitive Edition* releases.
[![github stars](https://img.shields.io/github/stars/SFTtech/openage.svg)](https://github.com/SFTtech/openage/stargazers)
[![#sfttech on matrix.org](/assets/doc/matrixroom.svg)](https://matrix.to/#/#sfttech:matrix.org)
@@ -13,32 +13,41 @@ To play, you require *an original AoE II: TC, [AoE II: HD](http://store.steampow
Contact
-------
-Contact | Where?
------------------|-------
-Issue Tracker | [GitHub SFTtech/openage](https://github.com/SFTtech/openage/issues)
-Development Blog | [blog.openage.dev](https://blog.openage.dev)
-Subreddit | [![reddit](/assets/doc/reddit.svg) /r/openage](https://www.reddit.com/r/openage/)
-Discussions | [GitHub Discussions](https://github.com/SFTtech/openage/discussions)
-Matrix Chat | [![matrix](/assets/doc/matrix.svg) `#sfttech:matrix.org`](https://matrix.to/#/#sfttech:matrix.org)
-Money Sink | [![money sink](/assets/doc/liberapay.svg)](https://liberapay.com/SFTtech)
-
+| Contact | Where? |
+| ---------------- | -------------------------------------------------------------------------------------------------- |
+| Issue Tracker | [GitHub SFTtech/openage] |
+| Development Blog | [blog.openage.dev] |
+| Subreddit | [![reddit](/assets/doc/reddit.svg) /r/openage](https://www.reddit.com/r/openage/) |
+| Discussions | [GitHub Discussions] |
+| Matrix Chat | [![matrix](/assets/doc/matrix.svg) `#sfttech:matrix.org`](https://matrix.to/#/#sfttech:matrix.org) |
+| Money Sink | [![money sink](/assets/doc/liberapay.svg)](https://liberapay.com/SFTtech) |
+
+[GitHub SFTtech/openage]: https://github.com/SFTtech/openage/issues
+[blog.openage.dev]: https://blog.openage.dev
+[GitHub Discussions]: https://github.com/SFTtech/openage/discussions
Technical foundation
--------------------
-Technology | Component
----------------|----------
-**C++20** | Engine core
-**Python3** | Scripting, media conversion, in-game console, code generation
-**Qt6** | Graphical user interface
-**Cython** | Python/C++ Glue code
-**CMake** | Build system
-**OpenGL3.3** | Rendering, shaders
-**SDL2** | Cross-platform Audio/Input/Window handling
-**Opus** | Audio codec
-[**nyan**](https://github.com/SFTtech/nyan) | Content Configuration and Modding
-**Humans** | Mixing together all of the above
-
+| Technology | Component |
+| ------------ | ------------------------------------------------------------- |
+| **C++20** | Engine core |
+| **Python3** | Scripting, media conversion, in-game console, code generation |
+| [**Cython**] | Python/C++ Glue code |
+| [**Qt6**] | Graphical user interface |
+| [**CMake**] | Build system |
+| [**OpenGL**] | Rendering, shaders |
+| [**Opus**] | Audio codec |
+| [**nyan**] | Content Configuration and Modding |
+| [**Humans**] | Mixing together all of the above |
+
+[**Cython**]: https://cython.org/
+[**Qt6**]: https://contribute.qt-project.org/
+[**CMake**]: https://cmake.org/
+[**OpenGL**]: https://www.opengl.org/
+[**Opus**]: https://opus-codec.org/
+[**nyan**]: https://github.com/SFTtech/nyan
+[**Humans**]: https://www.youtube.com/watch?v=fQGbXmkSArs&t=20s
Goals
-----
@@ -72,26 +81,19 @@ Current State of the Project
We're implementing the internal game simulation (how units even do anything) with simplicity and extensibility in mind, so we had to get rid of the temporary (but kind of working) previous version.
With these changes we can (finally) actually make use of our converted asset packs and our nyan API!
We're working day and night to make gameplay return\*.
-If you're interested, we wrote detailed explanations on our blog: [Part 1](https://blog.openage.dev/new-gamestate-2020.html), [Part 2](https://blog.openage.dev/engine-core-modules.html).
+If you're interested, we wrote detailed explanations on our blog: [Part 1](https://blog.openage.dev/new-gamestate-2020.html), [Part 2](https://blog.openage.dev/engine-core-modules.html), [Monthly Devlog](https://blog.openage.dev/tag/news.html).
*\* may not actually be every day and night*
-Operating System | Build status
-:------------------:|:--------------:
-Debian Sid | [Todo: Kevin #11](https://github.com/SFTtech/kevin/issues/11)
-Ubuntu 22.04 LTS | [![Ubuntu 22.04 build status](https://github.com/SFTTech/openage/actions/workflows/ubuntu-22.04.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/ubuntu-22.04.yml)
-macOS | [![macOS build status](https://github.com/SFTtech/openage/workflows/macOS-CI/badge.svg)](https://github.com/SFTtech/openage/actions?query=workflow%3AmacOS-CI)
-Windows Server 2019 | [![Windows Server 2019 build status](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml)
-Windows Server 2022 | [![Windows Server 2022 build status](https://github.com/SFTtech/openage/actions/workflows/windows-server-2022.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/windows-server-2022.yml)
-
-* Which **platforms** are supported?
- * Linux (primary), Windows & macOS (at least we try :)
-
-* What **features** are currently implemented?
- * See [status page](https://github.com/SFTtech/openage/projects).
+| Operating System | Build status |
+| :-----------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| Debian Sid | [Todo: Kevin #11] |
+| Ubuntu 22.04 LTS | [![Ubuntu 22.04 build status](https://github.com/SFTTech/openage/actions/workflows/ubuntu-22.04.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/ubuntu-22.04.yml) |
+| macOS | [![macOS build status](https://github.com/SFTtech/openage/workflows/macOS-CI/badge.svg)](https://github.com/SFTtech/openage/actions?query=workflow%3AmacOS-CI) |
+| Windows Server 2019 | [![Windows Server 2019 build status](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml) |
+| Windows Server 2022 | [![Windows Server 2022 build status](https://github.com/SFTtech/openage/actions/workflows/windows-server-2022.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/windows-server-2022.yml) |
-* What's the **plan**?
- * See [doc/milestones.md](/doc/milestones.md). We also have [lists of crazy xor good ideas](/doc/ideas) and a [technical overview for requested features](/doc/ideas/fr_technical_overview.md).
+[Todo: Kevin #11]: https://github.com/SFTtech/kevin/issues/11
Installation Packages
@@ -116,15 +118,21 @@ If you need help, maybe our [troubleshooting guide](/doc/troubleshooting.md) hel
Quickstart
----------
-* How do I get this to run on my box?
- * See [doc/building.md](/doc/building.md).
+* **How do I get this to run on my box?**
+ 1. [Clone](https://docs.github.com/repositories/creating-and-managing-repositories/cloning-a-repository) the repo.
+ 2. Install dependencies. See [doc/building.md](/doc/building.md#dependency-installation) to get instructions for your favorite platform.
+ 3. Build the project:
+ ```
+ ./configure --download-nyan
+ make
+ ```
-* I compiled everything. Now how do I run it?
+* **I compiled everything. Now how do I run it?**
* Execute `./bin/run`.
* [The convert script](/doc/media_convert.md) will transform original assets into openage formats, which are a lot saner and more moddable.
* Use your brain and react to the things you'll see.
-* Waaaaaah! It
+* **Waaaaaah! It...**
* segfaults
* prints error messages I don't want to read
* ate my dog
@@ -141,26 +149,14 @@ or the [bug tracker](https://github.com/SFTtech/openage/issues).
Contributing
============
-You might ask yourself now "Yeah, this sounds cool and all, but how do *I* participate
+You might ask yourself now "Sounds cool, but how do I participate
and ~~get famous~~ contribute useful features?".
Fortunately for you, there is a lot to do and we are very grateful for help.
## Where do I start?
-* The engine has several [core parts](https://github.com/SFTtech/openage/projects) that need help.
- You can look at the project related issues and find something for you, for example:
- * **Asset Converter:** Converts whatever proprietary format used by a Age of Empires 2 into
- open formats. Written mostly in Python 3. There are a lot of TODOs and beginner issues available
- right now, so it's a good place to get your feet wet.
- * **Game simulation:** Also known as the gameplay implementation. Written in C++, using the
- Entity-Component-System paradigm in addition to an event-driven simulation.
- * **Documentation:** We not only document code, but also anything technical about the Genie engine
- and its games. If you like documenting [file formats](/doc/media)
- or thoroughly investigating [game mechanics](/doc/reverse_engineering),
- then this might be the right place to start.
-* **Check the issues** [labelled with good first issues](https://github.com/SFTtech/openage/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). These are tasks that you can start right away
- and don't require much previous knowledge.
+* **Check the issues** [labelled with `good first issue`](https://github.com/SFTtech/openage/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). These are tasks that you can start right away and don't require much previous knowledge.
* **Ask us** in the [chat](https://matrix.to/#/#sfttech:matrix.org). Someone there could need
help with something.
* You can also **take the initiative** and fix a bug you found, create an issue for discussion or
diff --git a/doc/ai/interface.md b/doc/ai/interface.md
deleted file mode 100644
index 3ac52bbdf6..0000000000
--- a/doc/ai/interface.md
+++ /dev/null
@@ -1,93 +0,0 @@
-AI Interface
-============
-
-This is a very early draft with some ideas taken
-from http://bwmirror.jurenka.sk/javadoc/bwapi/package-summary.html
-
-This file shall provide information about the AI interface design.
-
-
-General ideas
--------------
-
- * An AI registers hooks for desired events
- * The C++ engine triggers python AI functions when desired events occur
- * The AI then can trigger actions on the controllable units
- * Periodically (1 Hz?), the AI gets ticked to allow event independent decisions
-
-
-Event types
------------
-
-Python interface basic events:
-
- * on_start(GameInfo info)
- * on_frame(Game game)
- * on_end(Player winner)
-
-Some AIs may prefer an event driven approach (rule based AI):
-
- * on_unit_discover(Unit unit)
- * on_unit_complete(Unit unit)
- * on_unit_lost(Unit unit)
- * many more...
-
-
-Some AIs will need to simulate ticks into the future to help
-make decisions for the current tick. (min-max, MCTS, etc.)
-
-
-Relevant structures for the AI
-------------------------------
-
-Remember: These are just some ideas for possible interfaces,
-they are not existent in the game yet.
-
-```cpp
-struct game_info {
- int num_players;
- int map_size;
- int pop_limit;
- resources_type;
- map_view;
- starting_age;
- victory_mode;
-}
-```
-
-```cpp
-class Game {
- Player[] allies(); // all the ally players that have not left or been defeated.
- bool canBuildHere(Unit builder, TilePosition position, UnitType type);
- bool canMake(Unit builder, UnitType type);
- bool canResearch(Unit unit, TechType type);
- bool canUpgrade(Unit unit, UpgradeType type);
- Player[] enemies(); // all the enemy players that have not left or been defeated.
- Unit[] getAllUnits(); // returns all the visible units.
-
- // many more examples at: http://bwmirror.jurenka.sk/javadoc/bwapi/Game.html
-}
-```
-
-```cpp
-class Player {
- string getName(); // returns the name of the player.
- Race getRace(); // returns the race of the player.
- Unit[] getUnits(); // returns the set of units the player own.
- bool hasResearched(TechType tech);
- int getWood() // Returns the amount of wood the player owns.
- // many more...
-}
-```
-
-```cpp
-class Unit {
- // used to get information about individual units as well as issue orders to units
-}
-```
-
-```cpp
-class Calculation {
- // used to get computation heavy calculations from the C++ engine
-}
-```
diff --git a/doc/architecture.md b/doc/architecture.md
deleted file mode 100644
index 154a303361..0000000000
--- a/doc/architecture.md
+++ /dev/null
@@ -1,74 +0,0 @@
-# openage architecture
-
-openage is separated into many modules ("subsystems").
-
-
-## Overview
-
-Some of the components are already implemented, others are not.
-All of them need to be revisited to implement the goal architecture.
-
-
-### Components
-
-* Audio system
-* Configuration system
-* [Coordinate system](code/coordinate-systems.md)
-* [Converter](code/converter/)
-* Input system
-* Networking
-* [Game rule database](nyan/)
-* [Pathfinding](code/pathfinding.md)
-* Rendering
-* Simulation
-* [User interface](code/gui.md)
-
-
-### Utilities
-
-* Datastructures
-* Error handling
-* Filesystem abstraction
-* Job dispatching
-* Live reloading
-* Logging system
-* Python interface
-* Random number generator
-* Test and demo infrastructure
-
-
-## Information flow
-
-### Current architecture
-
-The current data flow of openage is just to display the raw simulation data.
-
-```
-input -> simulation -> renderer -> output
-```
-
-
-### Goal architecture
-
-```
-0 input ->
-1 network ->
-2 simulation ->
-3 network ->
-4 prediction/interpolation ->
-5 renderer ->
-6 output
-```
-
-For *singleplayer* we could bypass `1 network` and `3 network` by using direct function calls or talk to a local socket.
-For *multiplayer* this means that we will have a single authoritative server that is just running
-the simulation. Each client then receives the data visible for it.
-
-The current architecture can be extended by adding the missing components in
-between.
-
-The `prediction/interpolation` reuses the `simulation` code, but is
-non-authoritative: The data provided from `2` has higher priority.
-
-There exists a link of `0 input -> 4 prediction` so that input
-is immediately accounted in the prediction, thus displayed.
diff --git a/doc/code/architecture.md b/doc/code/architecture.md
new file mode 100644
index 0000000000..a45cc73dd9
--- /dev/null
+++ b/doc/code/architecture.md
@@ -0,0 +1,103 @@
+# openage architecture
+
+openage is separated into many modules ("subsystems").
+
+1. [Overview](#overview)
+ 1. [Components](#components)
+ 2. [Utilities](#utilities)
+2. [Information flow](#information-flow)
+ 1. [Current architecture](#current-architecture)
+ 2. [Goal architecture](#goal-architecture)
+
+
+## Overview
+
+Some of the components are already implemented, others are not.
+All of them need to be revisited to implement the goal architecture.
+
+
+### Components
+
+* Audio system
+* Configuration system
+* [Coordinate system](coordinate-systems.md)
+* [Converter](converter/)
+* [Input system](input/)
+* Networking
+* [Game data database](/doc/nyan/)
+* [Rendering](renderer/)
+* [Simulation](game_simulation/)
+* [Time management](time.md)
+* [User interface](gui.md)
+
+
+### Utilities
+
+* Datastructures
+ * [Curves](curves.md)
+* [Error handling](exceptions.md)
+* Filesystem abstraction
+* Job dispatching
+* Live reloading
+* [Logging system](logger.md)
+* [Python interface](pyinterface.md)
+* Random number generator
+* [Test and demo infrastructure](testing.md)
+
+
+## Information flow
+
+### Current architecture
+
+The current data flow of openage is just to display the raw simulation data.
+
+![Engine architecture workflow](images/engine_architecture.svg)
+
+The presenter, simulation, and time subsystems each run in a loop in their own threads.
+They are largely decoupled from each other and only communicate via defined interfaces.
+
+Communication between all subsystems forms a larger *main* loop that encompasses everything
+happening in an engine run. It's workflow is roughly like this:
+
+```
+renderer (window system) -> input -> event system -> simulation -> renderer -> output
+```
+
+openage does not have simulation steps or *ticks* since everything in the simulation is
+event-based and scheduled by time. Therefore, updates between threads should generally work
+asynchronously.
+
+Decoupling allows us to treat some subsystems as optional such as the renderer and input
+system (basically everything from the presenter).
+
+
+### Goal architecture
+
+The goal architecture extends the current workflow by subsystems for networking and
+scripting:
+
+![Goal architecture workflow](images/engine_target_architecture.svg)
+
+Both of these are supposed to be decoupled and optional. The current architecture can be extended
+by adding the missing components in between.
+
+Networking forwards the relevant events and simulation parameters during multiplayer.
+We will have a single authoritative server that is also running the simulation asynchronously.
+Each client then receives the data visible for it.
+
+Scripting extends the event system with external sources targets for events. Most
+scripting should be integrated using event logic.
+
+The new workflow would then look something like this:
+
+```
+ ---------> scripting
+ | ^
+ | |
+ v v
+renderer (window system) -> input -> event system -> simulation -> renderer -> output
+ ^ ^
+ | |
+ | v
+ ---------> network
+```
diff --git a/doc/code/converter/architecture_overview.md b/doc/code/converter/architecture_overview.md
index a1f57917b6..9247952246 100644
--- a/doc/code/converter/architecture_overview.md
+++ b/doc/code/converter/architecture_overview.md
@@ -2,6 +2,14 @@
This document describes the code architecture of the openage converter module.
+1. [Design Principles](#design-principles)
+ 1. [Value Object](#value-object)
+ 2. [Entity Object](#entity-object)
+ 3. [Service](#service)
+ 4. [Processor](#processor)
+ 5. [Tool](#tool)
+
+
## Design Principles
Our converter uses hierarchical multi-layered object-oriented code principles similar to those found in
diff --git a/doc/code/converter/workflow.md b/doc/code/converter/workflow.md
index 427be6052c..5ddd2db621 100644
--- a/doc/code/converter/workflow.md
+++ b/doc/code/converter/workflow.md
@@ -8,6 +8,17 @@ must test with all supported version when you add a feature.
![workflow structogram](images/workflow.svg)
+1. [Game and Version detection](#game-and-version-detection)
+2. [Mounting](#mounting)
+3. [Reader](#reader)
+ 1. [Game Data](#game-data)
+4. [Processor](#processor)
+ 1. [Preprocessing](#preprocessing)
+ 2. [Grouping/Linking](#groupinglinking)
+ 3. [Mapping](#mapping)
+5. [Exporter](#exporter)
+
+
## Game and Version detection
The conversion process starts with determining the game version. Users
diff --git a/doc/code/coordinate-systems.md b/doc/code/coordinate-systems.md
index 284575f1db..c537074a1e 100644
--- a/doc/code/coordinate-systems.md
+++ b/doc/code/coordinate-systems.md
@@ -205,7 +205,7 @@ viewport --> normalized device space (Eigen::Vector2f)
The translations make use of current coordinate state (e.g. scroll position,
stored in `CoordManager`) and additional infos to solve
-degrees of freedom (see [below](#Parameters)).
+degrees of freedom (see [below](#parameters)).
* `input` -> `viewport`: just flip the y axis
diff --git a/doc/code/curves.md b/doc/code/curves.md
new file mode 100644
index 0000000000..2ead4a8c77
--- /dev/null
+++ b/doc/code/curves.md
@@ -0,0 +1,223 @@
+# Curves
+
+*Curves* are data structures that manage changes to data over time. More precisely,
+they do not only store data but time-value pairs (keyframes) that allow the retrieval
+of a curve data value for any point in time.
+
+Curves are an integral part of openage's event-based game simulation.
+
+1. [Motivation](#motivation)
+2. [Architecture](#architecture)
+3. [Curve Types](#curve-types)
+ 1. [Primitive](#primitive)
+ 1. [Common Operations](#common-operations)
+ 2. [Discrete](#discrete)
+ 3. [Continuous](#continuous)
+ 4. [Segmented](#segmented)
+ 2. [Container](#container)
+ 1. [Queue](#queue)
+ 2. [Unordered Map](#unordered-map)
+
+
+## Motivation
+
+The openage curve structures are inspired by a [similar implementation](https://www.forrestthewoods.com/blog/tech_of_planetary_annihilation_chrono_cam/#.lmxbu3vld) from the game Planetary Annihilation.
+
+Curves intend to solve synchronicity problems in games. In traditional implementations
+like lockstep, data is changed incrementally in regular intervals (ticks). Keeping
+these changes and the overall gamestate consistent/in sync over network or across threads
+is very important, as one missing change can result in a desync of the entire simulation.
+Recovering from a desync can also be very hard.
+
+In comparison, simulation with curves allows both async operations and an easier recovery
+from desync states. Data is not changed incrementally but is instead calculated using
+*keyframe interpolation*. In other words, changes to data are treated as keyframes on
+a timeline with in-between values being interpolated from adjacent keyframes.
+
+![Curve keyframes example](images/continuous_curve.png)
+
+Additionally, curves cannot only access values for the current simulation time but also for
+any past or even future times. Keyframes can also be inserted for any point in time without
+directly invalidating the state, making curves more reliable in async scenarios (although
+resolving dependencies for keyframes in the past can still be challenging).
+
+The usage of curves has a few downsides though. They are less space efficient due to the
+keyframe storage, interpolation are more costly more costly than incremental changes, and
+their integration is more complex than the usage of simpler data structures. However, in
+situations where operations are predictable, long-lasting, and easy to calculate - which
+is the case for most RTS games - the positives may outweigh the downsides.
+
+## Architecture
+
+![Curve classes hierarchy UML](images/curve_classes_uml.svg)
+
+openage provides several curve types with different interpolation behaviour that each have
+their specific use case. Primitive data types are covered by the `BaseCurve` interface
+and its derivates of which the `Discrete`, `Continuous`, and `Segmented` are the practically
+usable types. For containers, there are classes for `Queue` and `UnorderedMap` available.
+All curve types are templated, so that they can store any type of value (with some constraints).
+The usable types are explained in more detail in the [Curve Types](#curve-types) section.
+
+Keyframes are implemented as time-value pairs by the `Keyframe` class. `KeyframeContainer`
+is used by all curves to manage their keyframe storage. It also provides functionality to
+efficiently access and insert keyframes as wel as sorting them by time. For the time
+type, the [simulation time type](/doc/code/time.md) from the `openage::time` namespace is
+used.
+
+It should be noted that curves are not useful in every situation as keyframe insertion,
+interpolation, and searching keyframes based on time create significant overhead. Curves
+should be used for variables or members where
+
+* data values must be tracked over time (e.g. HP of a game entity), or
+* data values are not modified/read very often (e.g. not every frame), or
+* data values are supposed to be sent over the network.
+
+This is usually the case for all game data in [game entity components](/doc/code/game_simulation/components.md)
+inside the game simulation.
+
+Curves should generally not be used for variables or members where
+
+* data values are not tracked over time (e.g. for temporary variables), or
+* data values are modified/read very often (e.g. in the rendering loop), or
+* data values don't affect the simulation state (e.g. visual settings).
+
+
+## Curve Types
+
+This section provides an overview over the available curves types.
+
+### Primitive
+
+![Primitive curve types UML](images/primitive_curves_uml.svg)
+
+Primitive curves are intended for members with single value types. These include, for example,
+the primitive C++ types (e.g. `int`, `float`, `std::string`), simple structs and data classes,
+and shared pointers.
+
+On contruction of a primitive curve object, a keyframe with time `t = time::time_t::min_value()`
+and the value's type is instanciated with its default constructor. This is done to ensure that for any
+requested time `t`, there is always a valid value to be returned. This mirrors the expected
+behaviour from declaring primitive values in C++ where members may be auto-initialized without
+explicit assignment to a default value. The default value for curves can also be explicitely
+assigned in the constructor. Types that don't have a default constructor require that a
+default value is passed to the curve constructor.
+
+`BaseCurve` objects can be targeted by or trigger events from the [event system](/doc/code/event_system.md).
+As a consequence, curves are not copy constructable as they require a unique ID for
+event management. However, it is possible to copy the keyframes of one curve to
+the other using the `sync(..)` method. `sync(..)` also works for curves with different
+values types if a converter function from one value type to the other is supplied.
+
+#### Common Operations
+
+All primitive curves support the following operations. They may work slightly different
+for specific curve types.
+
+**Read**
+
+Read operations retrieve values for a specific point in time.
+
+| Method | Description |
+| --------------- | ----------------------------------------------------------- |
+| `get(t)` | Get (interpolated) value at time `t` |
+| `frame(t)` | Get the previous keyframe (time and value) before or at `t` |
+| `next_frame(t)` | Get the next keyframe (time and value) after `t` |
+
+**Modify**
+
+Modify operations insert values for a specific point in time.
+
+| Method | Description |
+| ----------------------- | --------------------------------------------------------------------------------- |
+| `set_insert(t, value)` | Insert a new keyframe value at time `t` |
+| `set_last(t, value)` | Insert a new keyframe value at time `t`; delete all keyframes after time `t` |
+| `set_replace(t, value)` | Insert a new keyframe value at time `t`; remove all other keyframes with time `t` |
+
+**Copy**
+
+Copy operations transfer keyframes from one curve to another.
+
+| Method | Description |
+| ---------------- | ------------------------------------------------------------------------------------------------ |
+| `sync(Curve, t)` | Replace all keyframes from self after time `t` with keyframes from source `Curve` after time `t` |
+
+
+#### Discrete
+
+![Discrete curve function example](images/discrete_curve.png)
+
+Discrete curves implement **constant interpolation** between keyframes. This means
+that the value returned by `get(t)` is always equal to the value of the previous
+keyframe.
+
+Discrete curves should be used for values that only change at specific points in time,
+e.g. for the health of a game entity.
+
+
+#### Continuous
+
+![Continuous curve function example](images/continuous_curve.png)
+
+Continuous curves implement **linear interpolation** between keyframes. This means
+that the value returned by `get(t)` is calculated from the value difference
+between the keyframes before and after `t`. If there is no keyframe after `t`,
+the value of the previous keyframe is used (like on discrete curves).
+
+Value types on continuous curves need to implement methods for the `operator*(..)` and
+`operator-(..)` operations to support linear interpolation. In particular, `operator*(..)`
+must support multiplication with `time::time_t` and `operator-(..)` must support
+substraction for values of the same type.
+
+Continuous curves do not allow jumps between keyframe values (hence "continuous").
+Therefore, there cannot be two keyframes inserted for the same time `t`.
+
+Continuous curves should be used for values that change gradually over time,
+e.g. a game entity's position or the construction progress of a building.
+
+
+#### Segmented
+
+![Segmented curve function example](images/segmented_curve.png)
+
+Segmented curves implement **linear interpolation** between keyframes and additionally
+allow jumps between keyframe values. As with continuous curves, the value returned by `get(t)`
+is calculated from the value difference between the keyframes before and after `t`.
+
+Jumps are inserted using the special methods `set_insert_jump(..)` and `set_last_jump(..)`:
+
+| Method | Description |
+| ------------------------------------ | --------------------------------------------------------------------------------- |
+| `set_insert_jump(t, value1, value2)` | Insert a two new keyframes at time `t`: `(t, value1)` and `(t, value2)` |
+| `set_last_jump(t, value1, value2)` | Insert a two new keyframes at time `t` like above; delete all keyframes after `t` |
+
+Segmented curves should be used for values that change gradually but are not on
+connected intervals. Typically, this are values that wrap around at some point,
+e.g. angles between 0 and 360 degrees.
+
+
+### Container
+
+Container curves are intended for storing changes to collections and containers.
+The currently supported containers are `Queue` and `UnorderedMap`.
+
+The most important distinction between regular C++ containers and curve containers
+is that curve containers keep track of when modifications happen and what changes
+to an element are made. Deleting elements also does not erase elements from memory.
+Instead, they are simply hidden for requests for time `t1` after the deletion time `t2` if
+`t1 > t2`.
+
+
+#### Queue
+
+Queue curve containers store elements in first-in-first-out (FIFO) insertion order
+while additionally keeping track of element insertion time. Requests for the front element
+at time `t` will return the element that is in front of the queue at that time.
+The queue can also be iterated over for a specific time `t` which allows access to
+all elements that were in the queue at time `t`.
+
+#### Unordered Map
+
+Unordered map curve containers store key-value pairs while additionally keeping
+track of element insertion time. Requests for a key `k` at time `t` will return the value
+of `k` at that time. The unordered map can also be iterated over for a specific time `t` which
+allows access to all key-value pairs that were in the map at time `t`.
diff --git a/doc/code/event_system.md b/doc/code/event_system.md
new file mode 100644
index 0000000000..762583df3f
--- /dev/null
+++ b/doc/code/event_system.md
@@ -0,0 +1,111 @@
+# Event System
+
+Game simulation in openage is event-driven. The internal event system
+manages the creation, scheduling, and execution of these events.
+
+1. [Architecture](#architecture)
+2. [Event Handler Setup](#event-handler-setup)
+3. [Event Instances](#event-instances)
+4. [Triggering/Targeting Events](#triggeringtargeting-events)
+5. [Canceling Events](#canceling-events)
+
+
+## Architecture
+
+![event system UML](images/event_classes_uml.svg)
+
+The central component of the event system is the *event loop* (`EventLoop`). It is
+the main point of interaction between the game simulation logic and the event system.
+New events (`Event` instances) are added via the `create_event(..)` method and scheduled based on the
+configuration of the associated *[event handler](#event-handler-setup)* (`EventHandler`).
+Internally, the event is stored inside an *event queue* (`EventQueue`) instance
+sorts the events by invoke time.
+
+Events are executed by passing the current [simulation time](time.md) to the
+event loop's `reach_time(..)` method. This executes all not-yet-executed events
+until the given target time stamp
+
+```
+t_old < t_e <= t_cur
+```
+
+where `t_cur` is the current simulation time, `t_old` is the last time `reach_time(..)` was
+called, and `t_e` is the event's scheduled invoke time. Events are executed in order of
+earliest to latest invoke time. During execution, events may trigger the rescheduling of
+other events if there are dependencies between the events.
+
+Both the creation and execution of events require passing a `State` object that functions
+as a persistant global storage for values that are not linked to a specific event executions.
+In practice, `State` is primarily used to pass the state of the [game simulation](/doc/code/game_simulation/)
+to the event system via an object of the derived `GameState` class. This object allows
+access to the indexing structures of the current game that can be used to retrieve
+the available game entities, for example.
+
+
+## Event Handler Setup
+
+Event effects on the game simulation are implemented by *event handlers*. Event handlers
+are attached to the event on creation and are invoked when the event is executed by the event
+loop. They are also used for (re-)scheduling the invocation time of the event.
+
+Event handlers are implemented as derivatives of the `EventHandler` class. Event handlers
+must implement 3 methods:
+
+* `invoke(..)`: Called by the event loop when the event is executed.
+* `predict_invoke_time(..)`: Called when the event handler shall determine a new invocation time of an event,
+ i.e. (re)place it on the event queue
+* `setup_event(..)`: Sets up dependencies during creation of the event.
+
+Additionally, event handlers require a unique ID (defined as a string) as well as
+a *trigger type* which defines under which circumstances an event is executed.
+These parameters can be defined at runtime when instantiating the event handler.
+
+New events require that an event handler object is attached during creation. This
+can be done by passing the event handler object to the `create_event(..)` method.
+If the event handler should be reusable, it can also be registered on the event loop
+via the `add_event_handler(..)` method. When calling `create_event(..)` on the event
+loop to create a new event, the event handler can then be referenced by a unique
+ID string.
+
+
+## Event Instances
+
+When calling `create_event(..)` on the event loop, an `Event` instance is returned which
+allows access to the configuration parameters of the event. Event instances store
+references to:
+
+* **Event target**: Entity targeted by the event, i.e. the event entity that the event effects should be applied on. Implemented as a weak reference to let the target expire.
+* **Event handler**: Event handler object used for rescheduling and executing the event.
+* **Invoke time**: Determines when the event is executed.
+* **Time of last change**: Time of the last change to invocation time. Used for rescheduling the event.
+* **Event payload**: Optional map of parameters passed to the event handler on invocation.
+
+These parameters may be changed after creating the event. Alternatively, it is possible to
+[cancel](#canceling-events) the event and create a new event with the updated parameters.
+
+
+## Triggering/Targeting Events
+
+To allow an event to modify an object in its invocation, the object's class must be
+a derivative of the `EventEntity` class. By inheriting from `EventEntity`, a class can
+be targeted by events, trigger or change events, or both.
+
+An event target is passed during the creation of an event (i.e. in `create_event(..)`)
+and later when executing the event via the event handler's `invoke(..)` method. Events
+can only have one target at a time.
+
+`EventEntity` derivatives may also be used to trigger or reschedule the invocation of events. To do this,
+the event has to be added as a dependent for the event entity using the `add_dependent(..)`
+method of `EventEntity`. Calling the `changes(..)` method of `EventEntity` will then result
+in a rescheduling of the event in the next iteration of the event loop.
+
+
+## Canceling Events
+
+Events are automatically canceled if their targeted event entity has expired, i.e. it has been deleted
+from memory. To accomplish this target references are implemented using `std::weak_ptr`
+which are checked for expiration before event execution.
+
+Manually canceling events can also be done by calling `cancel(..)` on an `Event` instance.
+In the current implementation, this change is effective immediately, even if the provided
+reference time is in the future.
diff --git a/doc/code/game_simulation/README.md b/doc/code/game_simulation/README.md
new file mode 100644
index 0000000000..0f8910a36b
--- /dev/null
+++ b/doc/code/game_simulation/README.md
@@ -0,0 +1,53 @@
+# Game Simulation
+
+*Game simulation* is the engine subsystem that implements game mechanics and data structures
+used for gameplay.
+
+The subsystem only contains components that are strictly necessary to simulate a game. Everything else,
+e.g. the [input system](/doc/code/input/), [rendering](/doc/code/renderer/),
+networking or scripting, are handled as separate subsystems.
+
+1. [Architecture](#architecture)
+2. [Workflow](#workflow)
+
+
+## Architecture
+
+![Game simulation overview UML](images/simulation_uml.svg)
+
+The game simulation is instantiated from the main thread via a `GameSimulation` object.
+This object controls the game loop, the current game session, and other simulation
+parameters necessary to run the game.
+
+The `Game` class represents a game session. Its instances store information about the
+gamestate via a `GameState` object which includes references to objects inside the game world
+(e.g. players or game entities). It also contains gameplay settings for the current session.
+
+"Physical" objects in the game world are represented by `Terrain` and `GameEntity`. `Terrain`
+is used to model the underlying map terrain, while [`GameEntity`](game_entity.md)
+is used for every type of world object (e.g. units, buildings, trees, resources, ambience).
+
+
+## Workflow
+
+To initiate a game, a `GameSimulation` object must be created. This is usually done by
+the `Engine` object in the main thread. On initialization, the game simulation automatically sets up
+ several subcomponents required for running the game such as:
+
+* [Event loop](/doc/code/event_system.md) for executing events
+* Entity factory for creating game entities
+* Mod manager for detecting/loading [openage modpacks](/doc/media/openage/modpacks.md)
+
+After the `GameSimulation` object is initialized, modpacks that should be loaded in
+the game session can be passed via the `set_modpacks(..)` method.
+
+The game simulation loop is started using the `run()` method of the `GameSimulation` object.
+`run()` should be run in its own thread with no other looping subsystems present. Before
+the simulation loop is entered, the simulation sets up a new game session (as a `Game`
+object). This loads the modpacks specified in the `set_modpacks(..)` method into the
+session.
+
+The logic of the simulation loop is quite simple as most of the execution happens in the
+[event system](/doc/code/event_system.md). In every loop iteration, the current simulation time is fetched from the
+[time subsystem](/doc/code/time.md). This time value is then passed to the simulation's
+event loop which executes all events queued until this time.
diff --git a/doc/code/game_simulation/activity.md b/doc/code/game_simulation/activity.md
new file mode 100644
index 0000000000..0b84864fe9
--- /dev/null
+++ b/doc/code/game_simulation/activity.md
@@ -0,0 +1,58 @@
+# Activity Control Flow
+
+The *activity control flow* is openage's method to make complex game entity behaviour
+configurable.
+
+1. [Motivation](#motivation)
+2. [Architecture](#architecture)
+3. [Node Types](#node-types)
+
+
+## Motivation
+
+Unit behaviour in RTS games can get very complex. In many cases, units are not
+just controlled by commands but other automated mechanisms, e.g. attacking enemy
+units in the line of sight. Furthermore, commands do not always translate to a single
+well-defined action. For example, an attack command usually results in a move action
+and a subsequent attack action. Some commands may even execute different actions depending
+on context.
+
+All this means that we cannot view the control flow of a unit as a simple mapping of
+command to action as is done in other games. Instead, we have to treat unit behaviour
+as a complex chain of actions with branching paths, wait states for events, triggers
+and feedback loops. Unless we want every command to be a hardcoded action chain, managing
+this complexity is key to making unit behaviour configurable.
+
+
+## Architecture
+
+Game entity control flows in openage are modelled as directed node graph, so-called *activities*.
+Nodes in the graph correspond to actions that execute for the game entity or conditional queries
+and event triggers that indicate which path to take next. By traversing the node graph along
+its paths, the game entities actions are determined. The currently visited node in the graph
+corresponds to the current action of a unit.
+
+Activities are reusable, i.e. they are intended to be shared by many game entities Usually,
+all game entities of the same type should share the same behaviour, so they get assigned
+the same activity node graph.
+
+An activity can also be represented visually like this:
+
+![graph example](images/activity_graph.svg)
+
+The design is heavily inpired by the [BPMN](https://en.wikipedia.org/wiki/Business_Process_Model_and_Notation)
+representation. You don't need to know BPMN to understand the activity control flow because
+we explain everything important about the graphs in our documentation. However,
+you can use available [BPMN tools](https://bpmn.io/) to draw activity node graphs.
+
+## Node Types
+
+
+| Type | Inputs | Outputs | Description |
+| ---------------- | ------ | ------- | ------------------------- |
+| `START` | 0 | 1 | Start of activity |
+| `END` | 1 | 0 | End of activity |
+| `TASK_SYSTEM` | 1 | 1 | Run built-in system |
+| `TASK_CUSTOM` | 1 | 1 | Run custom function |
+| `XOR_EVENT_GATE` | 1 | 1+ | Wait for event and branch |
+| `XOR_GATE` | 1 | 1+ | Branch on condition |
diff --git a/doc/code/game_simulation/components.md b/doc/code/game_simulation/components.md
new file mode 100644
index 0000000000..dc385a368b
--- /dev/null
+++ b/doc/code/game_simulation/components.md
@@ -0,0 +1,130 @@
+# Built-in Components
+
+Overview of the built-in game entity components in the game simulation.
+
+1. [Internal](#internal)
+ 1. [Activity](#activity)
+ 2. [CommandQueue](#commandqueue)
+ 3. [Ownership](#ownership)
+ 4. [Position](#position)
+2. [API](#api)
+ 1. [Idle](#idle)
+ 2. [Live](#live)
+ 3. [Move](#move)
+ 4. [Turn](#turn)
+
+
+## Internal
+
+Internal components do not have a corresponding nyan API object and thus only
+store runtime data.
+
+### Activity
+
+![Activity Component UML](images/component_activity_uml.svg)
+
+The `Activity` component stores a reference to the top-level activity for the
+game entity. Essentially, this gives access to the entire activity node graph
+used by the entity.
+
+Additionally, the current activity state is stored on a discrete curve that
+contains the last visited node.
+
+`Activity` also stores the handles of events initiated by the activity system
+for advancing to the next node. Once the next node is visited, these events
+should be canceled via the `cancel_events(..)` method.
+
+
+### CommandQueue
+
+![CommandQueue Component UML](images/component_activity_uml.svg)
+
+The `CommandQueue` component stores commands for the game entity in a [queue curve container](/doc/code/curves.md#queue).
+
+Commands in the queue use `Command` class derivatives which specify a command type
+and payload for the command.
+
+
+### Ownership
+
+![Ownership Component UML](images/component_ownership_uml.svg)
+
+The `Ownership` component stores the ID of the player who owns the game entity.
+
+
+### Position
+
+![Position Component UML](images/component_position_uml.svg)
+
+The `Position` component stores the location and direction of the game entity
+inside the game world.
+
+The 3D position of the game entity is stored on a continuous curve with value type
+`phys3`.
+
+Directions are stored as angles relative to the camera vector using clock-wise
+rotation. Here are some example values for reference to see how that works in
+practice:
+
+| Angle (degrees) | Direction |
+| --------------- | --------------------- |
+| 0 | look towards camera |
+| 90 | look left |
+| 180 | look away from camera |
+| 270 | look right |
+
+Angles are stored on a segmented curve.
+
+## API
+
+API components have a corresponding nyan API object of type `engine.ability.Ability` defined
+in the nyan API. This API object can be retrieved using the `get_ability(..)` method of the
+component.
+
+### Idle
+
+![Idle Component UML](images/component_idle_uml.svg)
+
+**nyan API object:** [`engine.ability.type.Idle`](/doc/nyan/api_reference/reference_ability.md#abilitytypeidle)
+
+The `Idle` component represents the ingame "idle" state of the game entity, i.e. when
+it is doing nothing.
+
+The component stores no runtime data.
+
+
+### Live
+
+![Live Component UML](images/component_live_uml.svg)
+
+**nyan API object:** [`engine.ability.type.Live`](/doc/nyan/api_reference/reference_ability.md#abilitytypelive)
+
+The `Live` component represents the game entity's ability to have attributes (e.g. health).
+
+An attribute's maximum limit is stored in the nyan API object, while
+the game entity's current attribute values are stored in the component
+on a discrete curve.
+
+
+### Move
+
+![Move Component UML](images/component_move_uml.svg)
+
+**nyan API object:** [`engine.ability.type.Move`](/doc/nyan/api_reference/reference_ability.md#abilitytypemove)
+
+The `Move` component represents the game entity's ability to move in the game world.
+This also allows moving the game entity with move commands.
+
+The component stores no runtime data.
+
+
+### Turn
+
+![Turn Component UML](images/component_turn_uml.svg)
+
+**nyan API object:** [`engine.ability.type.Turn`](/doc/nyan/api_reference/reference_ability.md#abilitytypeturn)
+
+The `Turn` component represents the game entity's ability to change directions in the game world.
+Turning is implicitely required for moving but it also works on its own.
+
+The component stores no runtime data.
diff --git a/doc/code/game_simulation/game_entity.md b/doc/code/game_simulation/game_entity.md
new file mode 100644
index 0000000000..b511a626a9
--- /dev/null
+++ b/doc/code/game_simulation/game_entity.md
@@ -0,0 +1,141 @@
+# Game Entity
+
+Game entities represent objects inside the game world.
+
+1. [Architecture](#architecture)
+2. [Game Entity class](#game-entity-class)
+3. [Component Data Storage](#component-data-storage)
+4. [Control Flow](#control-flow)
+ 1. [System](#system)
+ 2. [Activities](#activities)
+ 3. [Manager](#manager)
+
+
+## Architecture
+
+![Game entity UML](images/game_entity_overview.svg)
+
+Game entity mechanics are structured using the concept of *separation of concerns*.
+This means that there is not one single `GameEntity` class that implements all data
+and game logic. Instead, data and logic are separated into distinct engine components
+that each have a dedicated, clearly-defined purpose.
+
+Data storage is handled by `Component` objects that manage references to the [nyan](/doc/nyan/nyan.md)
+database and store runtime data on [curves](/doc/code/curves.md). Game logic is implemented
+in *systems*, which are best described as independent functions that perform a specific action
+on an entity. Event handling is done by the `GameEntityManager` which initiates and directs
+the entity's individual control flow.
+
+It should be noted that while the terminology we use here is very similar to names
+in the *Entity-Component-System* (ECS) architecture, you shouldn't think of the openage
+game simulation as a traditional ECS-driven architecture. We merely use this terminology
+because we can't think of anything better (and because we are too lazy to find better names).
+
+
+## Game Entity class
+
+![Game entity class UML](images/game_entity_uml.svg)
+
+The `GameEntity` class primarily provides references to the game entity's unique ID, assigned
+components, and event handling manager. If the game entity is supposed to be animated,
+a [render entity](/doc/code/renderer/level2.md#updating-render-stages-from-the-gamestate)
+can also be set. This allows requesting animations for the game entity.
+
+`GameEntity` follows the principle of *composition over inheritance*. Therefore, capabilities
+and attributes of a game entity are defined by the assigned components rather than properties
+of its class. The `GameEntity` class is merely a thin wrapper with a reference ID for
+a set of components storing the actual data (via `Component` subclasses). Components
+of the specific entity can be accessed via the `GameEntity` object's `get_component(..)` method.
+
+
+## Component Data Storage
+
+For a description of the available components, check the [component reference](components.md).
+
+![Component class UML](images/component_uml.svg)
+
+Components are data storage objects for a game entity that also perform the dual role
+of signifying what a game entity can do or is depending on their component type. Component
+types are usually associated with a subclass of `Component`, e.g. `Move`. For example, assigning
+a `Move` component to a game entity signifies that the game entity can perform move
+actions during gameplay. Movement data relevant to the game entity is then also stored in this
+component object.
+
+In general, component classes are designed to be atomic, i.e. they don't depend on other
+components to be assigned to the game entity. While some components may have a close relationship,
+e.g. `Move` and `Position`, it should never be assumed that all components are assigned
+when operating on a game entity.
+
+Persistent game data, i.e. data that is the same in every game like unit stats, is not
+managed by the component itself but in the game's [nyan](/doc/nyan/nyan.md) database. The component
+only holds a reference to the nyan object associated with the component. Data from nyan
+must be fetched via the [openage nyan interface](/doc/nyan/openage-lib.md).
+Component types usually have a corresponding nyan object type in the nyan API, e.g. the `Move`
+component corresponds to the `engine.ability.type.Move` nyan type.
+
+Runtime data, i.e. data that changes due to gameplay interactions, is stored on [curves](/doc/code/curves.md)
+managed by the component object. Curves are keyframe-based time-value storages that track
+changes to the data over time. Components can provide helper methods to interact with
+the curve, e.g. `Position` provides `set_position` to insert a new keyframe on its
+`position` data curve.
+
+
+## Control Flow
+
+The control flow is organized into 3 core mechanisms: Systems, activities, and
+managers. This again tries to achieve a *separation of concern* design to
+make the game logic maintanable and extensible.
+
+### System
+
+For a description of the available systems, check the [system reference](systems.md).
+
+A *system* in openage is basically a function that operates on game entity
+components. They are explicitely separated from game entity and component objects
+to allow for more flexible implementation. In practice, systems are implemented as static
+functions inside the `gamestate::system` namespace.
+
+Systems are *stateless*, so they don't remember anything about the game entity
+components they operate on. If something about the execution state should be remembered,
+it needs to be stored in a component. Additionally, system execution is always
+immediate. This means that a system never waits for events or for a certain amount
+of time. Waiting is handled by the [game entity manager](#manager) and the
+[activity control flow](#activities) instead.
+
+When adding new systems to the engine, interdependencies to other systems should
+be avoided at all cost, i.e. a system should never call another system directly.
+Exceptions should only be made for direct subsystems implementing subroutines
+or to avoid code redundancies. The reasoning behind this is that dependencies
+between systems may quickly become unmanageable.
+
+
+### Activities
+
+![Activity Example](images/activity_graph.svg)
+
+*Activities* connect systems together in a node graph to describe the overall control flow
+for a game entity. In openage, activity node graphs are used to model the complex behaviour
+behaviour and action chains of RTS game entities, while also allowing the behaviour to
+be as configurable as possible. One could also think of activities as a behaviour graph
+where paths are taken based on the inputs a game entity receives. The architecture
+of the activity control flow is described in more detail in the
+[activity control flow documentation](activity.md).
+
+A game entity's current activity state is stored in its `Activity` component. This component
+holds a reference to the activity node graph used by the entity as well as the
+last visited node. This node describes which action/behavioural state the
+game entity currently is in.
+
+Advancement to the next node can be initiated in several ways, depending on the
+[node type](activity.md#node-types) of the current node.
+It can happen automatically or be triggered by an event. In the latter case,
+the event is handled by the `GameEntityManager` which calls an activity *system*
+that processes the event to choose the next node.
+
+![Activity Workflow](images/activity_workflow.png)
+
+
+### Manager
+
+The game entity listens for events that target the entity and takes the
+associated action depending on the event type.
diff --git a/doc/code/game_simulation/images/activity_graph.svg b/doc/code/game_simulation/images/activity_graph.svg
new file mode 100644
index 0000000000..bf87f779fa
--- /dev/null
+++ b/doc/code/game_simulation/images/activity_graph.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/doc/code/game_simulation/images/activity_workflow.png b/doc/code/game_simulation/images/activity_workflow.png
new file mode 100644
index 0000000000..188d48d3cc
Binary files /dev/null and b/doc/code/game_simulation/images/activity_workflow.png differ
diff --git a/doc/code/game_simulation/images/component_activity_uml.svg b/doc/code/game_simulation/images/component_activity_uml.svg
new file mode 100644
index 0000000000..c05314eeff
--- /dev/null
+++ b/doc/code/game_simulation/images/component_activity_uml.svg
@@ -0,0 +1,82 @@
+
+
+
diff --git a/doc/code/game_simulation/images/component_command_queue_uml.svg b/doc/code/game_simulation/images/component_command_queue_uml.svg
new file mode 100644
index 0000000000..cabb92d98b
--- /dev/null
+++ b/doc/code/game_simulation/images/component_command_queue_uml.svg
@@ -0,0 +1,51 @@
+
+
+
diff --git a/doc/code/game_simulation/images/component_idle_uml.svg b/doc/code/game_simulation/images/component_idle_uml.svg
new file mode 100644
index 0000000000..439a7c39af
--- /dev/null
+++ b/doc/code/game_simulation/images/component_idle_uml.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/doc/code/game_simulation/images/component_live_uml.svg b/doc/code/game_simulation/images/component_live_uml.svg
new file mode 100644
index 0000000000..b7d47a963e
--- /dev/null
+++ b/doc/code/game_simulation/images/component_live_uml.svg
@@ -0,0 +1,33 @@
+
+
+
diff --git a/doc/code/game_simulation/images/component_move_uml.svg b/doc/code/game_simulation/images/component_move_uml.svg
new file mode 100644
index 0000000000..6076927c78
--- /dev/null
+++ b/doc/code/game_simulation/images/component_move_uml.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/doc/code/game_simulation/images/component_ownership_uml.svg b/doc/code/game_simulation/images/component_ownership_uml.svg
new file mode 100644
index 0000000000..141bd5c7ef
--- /dev/null
+++ b/doc/code/game_simulation/images/component_ownership_uml.svg
@@ -0,0 +1,33 @@
+
+
+
diff --git a/doc/code/game_simulation/images/component_position_uml.svg b/doc/code/game_simulation/images/component_position_uml.svg
new file mode 100644
index 0000000000..6f090fcb9e
--- /dev/null
+++ b/doc/code/game_simulation/images/component_position_uml.svg
@@ -0,0 +1,39 @@
+
+
+
diff --git a/doc/code/game_simulation/images/component_turn_uml.svg b/doc/code/game_simulation/images/component_turn_uml.svg
new file mode 100644
index 0000000000..9f06b9f662
--- /dev/null
+++ b/doc/code/game_simulation/images/component_turn_uml.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/doc/code/game_simulation/images/component_uml.svg b/doc/code/game_simulation/images/component_uml.svg
new file mode 100644
index 0000000000..06c5c3c74e
--- /dev/null
+++ b/doc/code/game_simulation/images/component_uml.svg
@@ -0,0 +1,170 @@
+
+
+
diff --git a/doc/code/game_simulation/images/game_entity_overview.svg b/doc/code/game_simulation/images/game_entity_overview.svg
new file mode 100644
index 0000000000..f57806cd39
--- /dev/null
+++ b/doc/code/game_simulation/images/game_entity_overview.svg
@@ -0,0 +1,73 @@
+
+
+
diff --git a/doc/code/game_simulation/images/game_entity_uml.svg b/doc/code/game_simulation/images/game_entity_uml.svg
new file mode 100644
index 0000000000..d3a24bdfd6
--- /dev/null
+++ b/doc/code/game_simulation/images/game_entity_uml.svg
@@ -0,0 +1,94 @@
+
+
+
diff --git a/doc/code/game_simulation/images/simulation_uml.svg b/doc/code/game_simulation/images/simulation_uml.svg
new file mode 100644
index 0000000000..36b67458cd
--- /dev/null
+++ b/doc/code/game_simulation/images/simulation_uml.svg
@@ -0,0 +1,134 @@
+
+
+
diff --git a/doc/code/game_simulation/images/system_idle.svg b/doc/code/game_simulation/images/system_idle.svg
new file mode 100644
index 0000000000..3da9289b05
--- /dev/null
+++ b/doc/code/game_simulation/images/system_idle.svg
@@ -0,0 +1,28 @@
+
+
+
diff --git a/doc/code/game_simulation/images/system_move.svg b/doc/code/game_simulation/images/system_move.svg
new file mode 100644
index 0000000000..46966a4b4f
--- /dev/null
+++ b/doc/code/game_simulation/images/system_move.svg
@@ -0,0 +1,30 @@
+
+
+
diff --git a/doc/code/game_simulation/systems.md b/doc/code/game_simulation/systems.md
new file mode 100644
index 0000000000..61ea2fcf34
--- /dev/null
+++ b/doc/code/game_simulation/systems.md
@@ -0,0 +1,36 @@
+# Built-in Systems
+
+Overview of the built-in systems in the game simulation.
+
+1. [Idle](#idle)
+2. [Move](#move)
+
+
+## Idle
+
+![Idle systems class UML](images/system_idle.svg)
+
+Handles idle actions for game entities.
+
+`idle(..)` updates the animation of the game entity. This requires the game
+entity to have the `Idle` component. The function returns a time of 0 since
+no actionsconsuming simulation time are taken.
+
+
+## Move
+
+![Move systems class UML](images/system_move.svg)
+
+Handles movement actions for game entities.
+
+`move_default(..)` moves a game entity to the new position specified in the function
+call. This requires the game entity to have the `Move` and `Turn` components.
+Waypoints for the exact path are fetched from the pathfinder.
+For every straight path between waypoints, the game entity is turned first, then
+moved (same as in *Age of Empires*). If an animation is available for the `Move`
+component, this animation is forwarded as the game entity's active animation to the
+renderer. The function returns the cumulative time of all turn and movement actions
+initiated by this function.
+
+`move_command(..)` processes the payload from a move *command* to call `move_default(..)`
+with the payload parameters.
diff --git a/doc/code/gui.md b/doc/code/gui.md
index 17196b4b09..6c548c2b65 100644
--- a/doc/code/gui.md
+++ b/doc/code/gui.md
@@ -1,5 +1,7 @@
# QtQuick-based Graphical User Interface
+**Important Notice**: This document is outdated and subject to change.
+
## Using Qt Creator Integrated Development Environment (IDE)
See [Qt Creator IDE](/doc/ide/qt_creator.md).
@@ -30,7 +32,7 @@ Example:
## Exposing components to the GUI layer
Components are adapted by writing QObject counterparts for them.
-Look for the examples in the `libopenage/gui` directory.
+Look for the examples in the `libopenage/renderer/gui` directory.
There is a property-based approach and a model-based extension to it.
@@ -40,7 +42,7 @@ Let's suppose we have a class `ResourceAmount` in `libopenage/economy`, and we w
In order to do that:
-1. A class `ResourceAmountLink` must be created in the `libopenage/gui`.
+1. A class `ResourceAmountLink` must be created in the `libopenage/renderer/gui`.
It must derive from `GuiItemQObject` and `GuiItem`.
It must be registered in the QML type system using a usual Qt approach:
```cpp
@@ -85,7 +87,7 @@ this->setv("player_radius", 10);
this->set_csv("player_names", std::vector{"name1", "name2"});
```
-3. A class `GeneratorParametersLink` must be created in the `libopenage/gui`.
+3. A class `GeneratorParametersLink` must be created in the `libopenage/renderer/gui`.
It must derive from `QObject` and `GuiItemListModel`.
It must be registered in the QML type system using a usual Qt approach:
```cpp
@@ -166,11 +168,11 @@ void EditorModeLink::on_core_adopted() {
### Including headers
-The files that are outside of the `libopenage/gui/` are allowed to include only the headers from the `libopenage/gui/guisys/public` and `libopenage/gui/integration/public`.
+The files that are outside of the `libopenage/renderer/gui/` are allowed to include only the headers from the `libopenage/renderer/gui/guisys/public` and `libopenage/renderer/gui/integration/public`.
## Directory structure
-The subsystem resides in the `libopenage/gui`.
+The subsystem resides in the `libopenage/renderer/gui`.
* random files in the directory - bindings for the game components
* `guisys/` - non-openage-specific part
diff --git a/doc/code/images/component_classes.uxf b/doc/code/images/component_classes.uxf
new file mode 100644
index 0000000000..7329e11d96
--- /dev/null
+++ b/doc/code/images/component_classes.uxf
@@ -0,0 +1,250 @@
+
+
+ // Uncomment the following line to change the fontsize and font:
+fontsize=14
+// fontfamily=SansSerif //possible: SansSerif,Serif,Monospaced
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Welcome to UMLet!
+//
+// Double-click on elements to add them to the diagram, or to copy them
+// Edit elements by modifying the text in this panel
+// Hold Ctrl to select multiple elements
+// Use Ctrl+mouse to select via lasso
+//
+// Use +/- or Ctrl+mouse wheel to zoom
+// Drag a whole relation at its central square icon
+//
+// Press Ctrl+C to copy the whole diagram to the system clipboard (then just paste it to, eg, Word)
+// Edit the files in the "palettes" directory to create your own element palettes
+//
+// Select "Custom Elements > New..." to create new element types
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// This text will be stored with each diagram; use it for notes.
+ 15
+
+ UMLClass
+
+ 720
+ 435
+ 420
+ 240
+
+ **CommandQueue**
+--
+command_queue: curve::Queue
+--
+add_command(time_t, Command): void
+get_queue(): curve::Queue
+
+
+
+ Text
+
+ 660
+ 0
+ 540
+ 105
+
+ **Components**
+style=wordwrap
+fontsize=20
+
+
+
+ UMLClass
+
+ 1260
+ 435
+ 195
+ 90
+
+
+**Command**
+
+
+
+ Relation
+
+ 1125
+ 465
+ 165
+ 45
+
+ lt=<.
+ 90.0;10.0;10.0;10.0
+
+
+ UMLClass
+
+ 720
+ 75
+ 420
+ 300
+
+ **Activity**
+--
+start_activity: Activity
+node: curve::Discrete
+scheduled_events: vector<Event>
+--
+get_start_activity(): Activity
+get_node(time_t): Node
+set_node(time_t, Node): void
+init(time_t): void
+add_event(Event): void
+cancel_events(): void
+
+
+
+
+ UMLClass
+
+ 1260
+ 75
+ 195
+ 90
+
+
+**Activity**
+
+
+
+ Relation
+
+ 1125
+ 105
+ 165
+ 45
+
+ lt=<.
+ 90.0;10.0;10.0;10.0
+
+
+ UMLClass
+
+ 1260
+ 210
+ 195
+ 90
+
+
+**Node**
+
+
+
+ Relation
+
+ 1125
+ 240
+ 165
+ 45
+
+ lt=<.
+ 90.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1350
+ 150
+ 45
+ 90
+
+ lt=<.
+ 10.0;40.0;10.0;10.0
+
+
+ UMLClass
+
+ 720
+ 720
+ 420
+ 240
+
+ **Ownership**
+--
+owner: curve::Discrete
+--
+set_owner(time_t, ownership_id_t): void
+get_owners(): curve::Discrete
+
+
+
+ UMLClass
+
+ 720
+ 1020
+ 420
+ 240
+
+ **Position**
+--
+position: curve::Continuous
+angle: curve::Segmented
+--
+get_positions(): curve::Continuous
+get_angles(): curve::Segmented
+set_position(time_t, coord::phys3): void
+set_angle(time_t, phys_angle_t): void
+
+
+
+ UMLClass
+
+ 855
+ 1305
+ 165
+ 90
+
+
+**Idle**
+
+
+
+
+ UMLClass
+
+ 720
+ 1455
+ 465
+ 240
+
+ **Live**
+--
+attribute_values: curve::UnorderedMap
+--
+add_attribute(time_t, fqon_t, curve::Discrete)
+set_attribute(time_t, fqon_t, int64_t): void
+
+
+
+ UMLClass
+
+ 855
+ 1740
+ 165
+ 90
+
+
+**Move**
+
+
+
+
+ UMLClass
+
+ 855
+ 1890
+ 165
+ 90
+
+
+**Turn**
+
+
+
+
diff --git a/doc/code/images/continuous_curve.ggb b/doc/code/images/continuous_curve.ggb
new file mode 100644
index 0000000000..e55f6ee926
Binary files /dev/null and b/doc/code/images/continuous_curve.ggb differ
diff --git a/doc/code/images/continuous_curve.png b/doc/code/images/continuous_curve.png
new file mode 100644
index 0000000000..a7bc237111
Binary files /dev/null and b/doc/code/images/continuous_curve.png differ
diff --git a/doc/code/images/curve_classes_uml.svg b/doc/code/images/curve_classes_uml.svg
new file mode 100644
index 0000000000..95295b7c13
--- /dev/null
+++ b/doc/code/images/curve_classes_uml.svg
@@ -0,0 +1,149 @@
+
+
+
diff --git a/doc/code/images/curves_classes.uxf b/doc/code/images/curves_classes.uxf
new file mode 100644
index 0000000000..351ad7255e
--- /dev/null
+++ b/doc/code/images/curves_classes.uxf
@@ -0,0 +1,418 @@
+
+
+ // Uncomment the following line to change the fontsize and font:
+fontsize=14
+// fontfamily=SansSerif //possible: SansSerif,Serif,Monospaced
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Welcome to UMLet!
+//
+// Double-click on elements to add them to the diagram, or to copy them
+// Edit elements by modifying the text in this panel
+// Hold Ctrl to select multiple elements
+// Use Ctrl+mouse to select via lasso
+//
+// Use +/- or Ctrl+mouse wheel to zoom
+// Drag a whole relation at its central square icon
+//
+// Press Ctrl+C to copy the whole diagram to the system clipboard (then just paste it to, eg, Word)
+// Edit the files in the "palettes" directory to create your own element palettes
+//
+// Select "Custom Elements > New..." to create new element types
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// This text will be stored with each diagram; use it for notes.
+ 15
+
+ UMLClass
+
+ 510
+ 120
+ 195
+ 90
+
+ <<interface>>
+**BaseCurve**
+bg=pink
+
+
+
+
+ UMLClass
+
+ 390
+ 510
+ 180
+ 90
+
+
+**Discrete**
+bg=green
+
+
+
+ UMLClass
+
+ 720
+ 315
+ 180
+ 90
+
+ <<interface>>
+**Interpolated**
+bg=pink
+
+
+
+
+ UMLClass
+
+ 615
+ 510
+ 180
+ 90
+
+
+**Continuous**
+bg=green
+
+
+
+
+ UMLClass
+
+ 840
+ 510
+ 165
+ 90
+
+
+**Segmented**
+bg=green
+
+
+
+
+ Relation
+
+ 510
+ 195
+ 45
+ 345
+
+ lt=<<-
+ 10.0;10.0;10.0;210.0
+
+
+ Relation
+
+ 660
+ 195
+ 90
+ 195
+
+ lt=<<-
+ 10.0;10.0;10.0;110.0;40.0;110.0
+
+
+ Relation
+
+ 750
+ 390
+ 45
+ 150
+
+ lt=<<-
+ 10.0;10.0;10.0;80.0
+
+
+ Relation
+
+ 855
+ 390
+ 45
+ 150
+
+ lt=<<-
+ 10.0;10.0;10.0;80.0
+
+
+ UMLClass
+
+ 930
+ 120
+ 255
+ 90
+
+
+**KeyframeContainer**
+
+
+
+
+ Relation
+
+ 690
+ 150
+ 270
+ 45
+
+ lt=<.
+ 160.0;10.0;10.0;10.0
+
+
+ UMLClass
+
+ 1275
+ 120
+ 150
+ 90
+
+
+**Keyframe**
+
+
+
+
+ Relation
+
+ 1170
+ 150
+ 135
+ 45
+
+ lt=<.
+ 70.0;10.0;10.0;10.0
+
+
+ UMLClass
+
+ 1050
+ 510
+ 165
+ 90
+
+
+**Queue**
+bg=green
+
+
+
+
+ UMLClass
+
+ 1260
+ 510
+ 195
+ 90
+
+
+**UnorderedMap**
+bg=green
+
+
+
+
+ Text
+
+ 300
+ 0
+ 375
+ 105
+
+ **Overview**
+style=wordwrap
+fontsize=20
+
+
+
+ Text
+
+ 315
+ 675
+ 375
+ 105
+
+ **Primitive Curves**
+style=wordwrap
+fontsize=20
+
+
+
+ UMLClass
+
+ 330
+ 765
+ 360
+ 270
+
+ <<interface>>
+**BaseCurve<T>**
+bg=pink
+--
+/get(time_t): T/
+frame(time_t): pair<time_t, T>
+next_frame(time_t): pair<time_t, T>
+set_insert(time_t, T): void
+set_last(time_t, T): void
+set_replace(time_t, T): void
+sync(BaseCurve<T>, time_t): void
+
+
+
+ UMLClass
+
+ 270
+ 1290
+ 270
+ 135
+
+ **Discrete**
+bg=green
+--
+get(time_t): T
+get_time(time_t): T
+get_previous(time_t): T
+
+
+
+ UMLClass
+
+ 765
+ 1095
+ 255
+ 120
+
+ <<interface>>
+**Interpolated**
+bg=pink
+--
+get(time_t): T
+
+
+
+ UMLClass
+
+ 600
+ 1290
+ 270
+ 135
+
+ **Continuous**
+bg=green
+--
+set_insert(time_t, T): void
+set_last(time_t, T): void
+
+
+
+ UMLClass
+
+ 930
+ 1290
+ 345
+ 135
+
+ **Segmented**
+bg=green
+--
+set_insert_jump(time_t, T, T): void
+set_last_jump(time_t, T, T): void
+
+
+
+
+ Relation
+
+ 420
+ 1020
+ 45
+ 300
+
+ lt=<<-
+ 10.0;10.0;10.0;180.0
+
+
+ Relation
+
+ 615
+ 1020
+ 180
+ 150
+
+ lt=<<-
+ 10.0;10.0;10.0;80.0;100.0;80.0
+
+
+ Relation
+
+ 780
+ 1200
+ 45
+ 120
+
+ lt=<<-
+ 10.0;10.0;10.0;60.0
+
+
+ Relation
+
+ 975
+ 1200
+ 45
+ 120
+
+ lt=<<-
+ 10.0;10.0;10.0;60.0
+
+
+ UMLClass
+
+ 915
+ 750
+ 255
+ 90
+
+
+**KeyframeContainer**
+
+
+
+
+ Relation
+
+ 675
+ 780
+ 270
+ 45
+
+ lt=<.
+ 160.0;10.0;10.0;10.0
+
+
+ UMLClass
+
+ 1260
+ 750
+ 150
+ 90
+
+
+**Keyframe**
+
+
+
+
+ Relation
+
+ 1155
+ 780
+ 135
+ 45
+
+ lt=<.
+ 70.0;10.0;10.0;10.0
+
+
diff --git a/doc/code/images/discrete_curve.ggb b/doc/code/images/discrete_curve.ggb
new file mode 100644
index 0000000000..c49ddd65c6
Binary files /dev/null and b/doc/code/images/discrete_curve.ggb differ
diff --git a/doc/code/images/discrete_curve.png b/doc/code/images/discrete_curve.png
new file mode 100644
index 0000000000..e3a27942a8
Binary files /dev/null and b/doc/code/images/discrete_curve.png differ
diff --git a/doc/code/images/engine_architecture.svg b/doc/code/images/engine_architecture.svg
new file mode 100644
index 0000000000..c24199612c
--- /dev/null
+++ b/doc/code/images/engine_architecture.svg
@@ -0,0 +1,269 @@
+
+
+
diff --git a/doc/code/images/engine_architecture.uxf b/doc/code/images/engine_architecture.uxf
new file mode 100644
index 0000000000..35b2297706
--- /dev/null
+++ b/doc/code/images/engine_architecture.uxf
@@ -0,0 +1,1092 @@
+
+
+ // Uncomment the following line to change the fontsize and font:
+fontsize=14
+// fontfamily=SansSerif //possible: SansSerif,Serif,Monospaced
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Welcome to UMLet!
+//
+// Double-click on elements to add them to the diagram, or to copy them
+// Edit elements by modifying the text in this panel
+// Hold Ctrl to select multiple elements
+// Use Ctrl+mouse to select via lasso
+//
+// Use +/- or Ctrl+mouse wheel to zoom
+// Drag a whole relation at its central square icon
+//
+// Press Ctrl+C to copy the whole diagram to the system clipboard (then just paste it to, eg, Word)
+// Edit the files in the "palettes" directory to create your own element palettes
+//
+// Select "Custom Elements > New..." to create new element types
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// This text will be stored with each diagram; use it for notes.
+ 15
+
+ UMLClass
+
+ 690
+ 345
+ 195
+ 90
+
+
+**Input**
+
+
+
+ UMLClass
+
+ 1020
+ 180
+ 195
+ 90
+
+
+**Renderer**
+
+
+
+ UMLClass
+
+ 690
+ 90
+ 195
+ 90
+
+
+**GUI**
+
+
+
+ UMLClass
+
+ 1035
+ 600
+ 195
+ 90
+
+
+**EventLoop**
+
+
+
+ UMLClass
+
+ 1275
+ 405
+ 195
+ 90
+
+
+**Simulation**
+
+
+
+ Text
+
+ 525
+ 15
+ 195
+ 60
+
+ **Presenter**
+fontsize=20
+
+style=wordwrap
+
+
+
+ Relation
+
+ 510
+ 0
+ 780
+ 45
+
+ lt=.
+ 10.0;10.0;500.0;10.0
+
+
+ Relation
+
+ 510
+ 0
+ 45
+ 600
+
+ lt=.
+ 10.0;380.0;10.0;10.0
+
+
+ Relation
+
+ 510
+ 555
+ 450
+ 45
+
+ lt=.
+ 280.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1245
+ 0
+ 45
+ 360
+
+ lt=.
+ 10.0;220.0;10.0;10.0
+
+
+ Relation
+
+ 915
+ 315
+ 375
+ 285
+
+ lt=.
+ 10.0;170.0;230.0;10.0
+
+
+ Text
+
+ 1620
+ 90
+ 105
+ 60
+
+ **Time**
+fontsize=20
+
+style=wordwrap
+
+
+
+ UMLClass
+
+ 1500
+ 180
+ 135
+ 90
+
+
+**Clock**
+
+
+
+ Relation
+
+ 1245
+ 75
+ 495
+ 45
+
+ lt=.
+ 10.0;10.0;310.0;10.0
+
+
+ Relation
+
+ 1695
+ 75
+ 45
+ 285
+
+ lt=.
+ 10.0;170.0;10.0;10.0
+
+
+ Relation
+
+ 1440
+ 315
+ 300
+ 45
+
+ lt=.
+ 180.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1695
+ 315
+ 45
+ 510
+
+ lt=.
+ 10.0;320.0;10.0;10.0
+
+
+ Relation
+
+ 1245
+ 315
+ 495
+ 45
+
+ lt=.
+ 10.0;10.0;310.0;10.0
+
+
+ Relation
+
+ 915
+ 780
+ 825
+ 45
+
+ lt=.
+ 530.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 915
+ 555
+ 45
+ 270
+
+ lt=.
+ 10.0;10.0;10.0;160.0
+
+
+ Relation
+
+ 840
+ 225
+ 210
+ 150
+
+ lt=<-
+ 10.0;80.0;10.0;10.0;120.0;10.0
+
+
+ Relation
+
+ 765
+ 165
+ 45
+ 210
+
+ lt=<-
+ 10.0;10.0;10.0;120.0
+
+
+ Relation
+
+ 870
+ 375
+ 255
+ 255
+
+ lt=<-
+ 150.0;150.0;150.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1185
+ 465
+ 120
+ 165
+
+ lt=<-
+ 60.0;10.0;10.0;10.0;10.0;90.0
+
+
+ Relation
+
+ 1155
+ 255
+ 150
+ 195
+
+ lt=<-
+ 10.0;10.0;10.0;110.0;80.0;110.0
+
+
+ Relation
+
+ 1455
+ 255
+ 150
+ 225
+
+ lt=<-
+ 10.0;130.0;80.0;130.0;80.0;10.0
+
+
+ Relation
+
+ 1200
+ 210
+ 330
+ 45
+
+ lt=<-
+ 10.0;10.0;200.0;10.0
+
+
+ Text
+
+ 840
+ 210
+ 225
+ 45
+
+ Key/Mouse events
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 630
+ 225
+ 150
+ 45
+
+ Mouse events
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 915
+ 390
+ 150
+ 45
+
+ Input events
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 1200
+ 510
+ 180
+ 45
+
+ Entity updates
+fontsize=12
+style=wordwrap
+
+
+
+ Relation
+
+ 1155
+ 450
+ 150
+ 180
+
+ lt=<-
+ 10.0;100.0;10.0;10.0;80.0;10.0
+
+
+ Text
+
+ 1500
+ 450
+ 180
+ 45
+
+ Current time
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 1110
+ 435
+ 180
+ 45
+
+ Scheduled events
+fontsize=12
+style=wordwrap
+
+
+
+ Relation
+
+ 1215
+ 435
+ 390
+ 240
+
+ lt=<-
+ 10.0;140.0;240.0;140.0;240.0;10.0
+
+
+ Text
+
+ 1440
+ 645
+ 180
+ 45
+
+ Current time
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 990
+ 285
+ 195
+ 45
+
+ Animation requests
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 1380
+ 195
+ 180
+ 45
+
+ Current time
+fontsize=12
+style=wordwrap
+
+
+
+ UMLClass
+
+ 690
+ 1215
+ 195
+ 90
+
+
+**Input**
+
+
+
+ UMLClass
+
+ 1020
+ 1050
+ 195
+ 90
+
+
+**Renderer**
+
+
+
+ UMLClass
+
+ 690
+ 960
+ 195
+ 90
+
+
+**GUI**
+
+
+
+ UMLClass
+
+ 1035
+ 1470
+ 195
+ 90
+
+
+**EventLoop**
+
+
+
+ UMLClass
+
+ 1275
+ 1275
+ 195
+ 90
+
+
+**Simulation**
+
+
+
+ Text
+
+ 525
+ 885
+ 195
+ 60
+
+ **Presenter**
+fontsize=20
+
+style=wordwrap
+
+
+
+ Relation
+
+ 510
+ 870
+ 780
+ 45
+
+ lt=.
+ 10.0;10.0;500.0;10.0
+
+
+ Relation
+
+ 510
+ 870
+ 45
+ 600
+
+ lt=.
+ 10.0;380.0;10.0;10.0
+
+
+ Relation
+
+ 510
+ 1425
+ 450
+ 45
+
+ lt=.
+ 280.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1245
+ 870
+ 45
+ 360
+
+ lt=.
+ 10.0;220.0;10.0;10.0
+
+
+ Relation
+
+ 915
+ 1185
+ 375
+ 285
+
+ lt=.
+ 10.0;170.0;230.0;10.0
+
+
+ Text
+
+ 1620
+ 960
+ 105
+ 60
+
+ **Time**
+fontsize=20
+
+style=wordwrap
+
+
+
+ UMLClass
+
+ 1500
+ 1050
+ 135
+ 90
+
+
+**Clock**
+
+
+
+ Relation
+
+ 1245
+ 945
+ 495
+ 45
+
+ lt=.
+ 10.0;10.0;310.0;10.0
+
+
+ Relation
+
+ 1695
+ 945
+ 45
+ 285
+
+ lt=.
+ 10.0;170.0;10.0;10.0
+
+
+ Relation
+
+ 1440
+ 1185
+ 300
+ 45
+
+ lt=.
+ 180.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1695
+ 1185
+ 45
+ 510
+
+ lt=.
+ 10.0;320.0;10.0;10.0
+
+
+ Relation
+
+ 1245
+ 1185
+ 495
+ 45
+
+ lt=.
+ 10.0;10.0;310.0;10.0
+
+
+ Relation
+
+ 915
+ 1650
+ 825
+ 45
+
+ lt=.
+ 530.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 915
+ 1425
+ 45
+ 270
+
+ lt=.
+ 10.0;10.0;10.0;160.0
+
+
+ Relation
+
+ 840
+ 1095
+ 210
+ 150
+
+ lt=<-
+ 10.0;80.0;10.0;10.0;120.0;10.0
+
+
+ Relation
+
+ 765
+ 1035
+ 45
+ 210
+
+ lt=<-
+ 10.0;10.0;10.0;120.0
+
+
+ Relation
+
+ 870
+ 1245
+ 255
+ 255
+
+ lt=<-
+ 150.0;150.0;150.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1185
+ 1335
+ 120
+ 165
+
+ lt=<-
+ 60.0;10.0;10.0;10.0;10.0;90.0
+
+
+ Relation
+
+ 1155
+ 1125
+ 150
+ 195
+
+ lt=<-
+ 10.0;10.0;10.0;110.0;80.0;110.0
+
+
+ Relation
+
+ 1455
+ 1125
+ 150
+ 225
+
+ lt=<-
+ 10.0;130.0;80.0;130.0;80.0;10.0
+
+
+ Relation
+
+ 1200
+ 1080
+ 330
+ 45
+
+ lt=<-
+ 10.0;10.0;200.0;10.0
+
+
+ Text
+
+ 840
+ 1080
+ 225
+ 45
+
+ Key/Mouse events
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 630
+ 1095
+ 150
+ 45
+
+ Mouse events
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 915
+ 1260
+ 150
+ 45
+
+ Input events
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 1200
+ 1380
+ 180
+ 45
+
+ Entity updates
+fontsize=12
+style=wordwrap
+
+
+
+ Relation
+
+ 1155
+ 1320
+ 150
+ 180
+
+ lt=<-
+ 10.0;100.0;10.0;10.0;80.0;10.0
+
+
+ Text
+
+ 1500
+ 1320
+ 180
+ 45
+
+ Current time
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 1110
+ 1305
+ 180
+ 45
+
+ Scheduled events
+fontsize=12
+style=wordwrap
+
+
+
+ Relation
+
+ 1215
+ 1305
+ 390
+ 240
+
+ lt=<-
+ 10.0;140.0;240.0;140.0;240.0;10.0
+
+
+ Text
+
+ 1440
+ 1515
+ 180
+ 45
+
+ Current time
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 990
+ 1155
+ 195
+ 45
+
+ Animation requests
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 1380
+ 1065
+ 180
+ 45
+
+ Current time
+fontsize=12
+style=wordwrap
+
+
+
+ UMLClass
+
+ 1035
+ 1770
+ 195
+ 90
+
+
+**Networking**
+
+
+
+ UMLClass
+
+ 660
+ 1470
+ 195
+ 90
+
+
+**Scripting**
+
+
+
+ Relation
+
+ 840
+ 1485
+ 225
+ 45
+
+ lt=<-
+ 130.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 840
+ 1515
+ 225
+ 45
+
+ lt=<-
+ 10.0;10.0;130.0;10.0
+
+
+ Relation
+
+ 1065
+ 1545
+ 45
+ 255
+
+ lt=<-
+ 10.0;150.0;10.0;10.0
+
+
+ Relation
+
+ 1110
+ 1545
+ 45
+ 255
+
+ lt=<-
+ 10.0;10.0;10.0;150.0
+
+
+ Relation
+
+ 1215
+ 1350
+ 225
+ 510
+
+ lt=<-
+ 130.0;10.0;130.0;320.0;10.0;320.0
+
+
+ Relation
+
+ 1215
+ 1350
+ 195
+ 480
+
+ lt=<-
+ 10.0;300.0;110.0;300.0;110.0;10.0
+
+
+ Text
+
+ 1335
+ 1830
+ 180
+ 45
+
+ Server updates
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 1275
+ 1755
+ 180
+ 45
+
+ Client data
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 1125
+ 1665
+ 180
+ 45
+
+ Server events
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 960
+ 1665
+ 180
+ 45
+
+ Client events
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 870
+ 1530
+ 180
+ 45
+
+ Event notifiers
+fontsize=12
+style=wordwrap
+
+
+
+ Text
+
+ 870
+ 1470
+ 180
+ 45
+
+ Script events
+fontsize=12
+style=wordwrap
+
+
+
diff --git a/doc/code/images/engine_target_architecture.svg b/doc/code/images/engine_target_architecture.svg
new file mode 100644
index 0000000000..fbc026c06a
--- /dev/null
+++ b/doc/code/images/engine_target_architecture.svg
@@ -0,0 +1,344 @@
+
+
+
diff --git a/doc/code/images/event_classes.uxf b/doc/code/images/event_classes.uxf
new file mode 100644
index 0000000000..d9be3fd902
--- /dev/null
+++ b/doc/code/images/event_classes.uxf
@@ -0,0 +1,177 @@
+
+
+ // Uncomment the following line to change the fontsize and font:
+fontsize=14
+// fontfamily=SansSerif //possible: SansSerif,Serif,Monospaced
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Welcome to UMLet!
+//
+// Double-click on elements to add them to the diagram, or to copy them
+// Edit elements by modifying the text in this panel
+// Hold Ctrl to select multiple elements
+// Use Ctrl+mouse to select via lasso
+//
+// Use +/- or Ctrl+mouse wheel to zoom
+// Drag a whole relation at its central square icon
+//
+// Press Ctrl+C to copy the whole diagram to the system clipboard (then just paste it to, eg, Word)
+// Edit the files in the "palettes" directory to create your own element palettes
+//
+// Select "Custom Elements > New..." to create new element types
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// This text will be stored with each diagram; use it for notes.
+ 15
+
+ UMLClass
+
+ 750
+ 450
+ 180
+ 90
+
+
+**EventLoop**
+
+
+
+ UMLClass
+
+ 1095
+ 450
+ 180
+ 90
+
+
+**EventQueue**
+
+
+
+ UMLClass
+
+ 1005
+ 645
+ 150
+ 90
+
+
+**Event**
+
+
+
+ UMLClass
+
+ 735
+ 780
+ 180
+ 90
+
+
+**EventHandler**
+
+
+
+ UMLClass
+
+ 585
+ 645
+ 120
+ 90
+
+
+**State**
+
+
+
+ Relation
+
+ 915
+ 480
+ 210
+ 45
+
+ lt=<.
+ 120.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 870
+ 525
+ 165
+ 195
+
+ lt=<.
+ 90.0;110.0;10.0;110.0;10.0;10.0
+
+
+ Relation
+
+ 1110
+ 525
+ 45
+ 150
+
+ lt=<.
+ 10.0;80.0;10.0;10.0
+
+
+ Relation
+
+ 795
+ 525
+ 45
+ 285
+
+ lt=<.
+ 10.0;170.0;10.0;10.0
+
+
+ Relation
+
+ 900
+ 720
+ 165
+ 135
+
+ lt=<.
+ 10.0;70.0;90.0;70.0;90.0;10.0
+
+
+ Relation
+
+ 630
+ 480
+ 150
+ 195
+
+ lt=<.
+ 10.0;110.0;10.0;10.0;80.0;10.0
+
+
+ UMLClass
+
+ 1185
+ 780
+ 150
+ 90
+
+
+**EventEntity**
+
+
+
+ Relation
+
+ 1095
+ 720
+ 120
+ 135
+
+ lt=<.
+ 60.0;70.0;10.0;70.0;10.0;10.0
+
+
diff --git a/doc/code/images/event_classes_uml.svg b/doc/code/images/event_classes_uml.svg
new file mode 100644
index 0000000000..9db8ffea01
--- /dev/null
+++ b/doc/code/images/event_classes_uml.svg
@@ -0,0 +1,124 @@
+
+
+
diff --git a/doc/code/images/game_entity_classes.uxf b/doc/code/images/game_entity_classes.uxf
new file mode 100644
index 0000000000..fedf65862a
--- /dev/null
+++ b/doc/code/images/game_entity_classes.uxf
@@ -0,0 +1,450 @@
+
+
+ 15
+
+ UMLClass
+
+ 0
+ 390
+ 390
+ 285
+
+ **GameEntity**
+--
+id: entity_id_t
+components: unordered_map
+render_entity: WorldRenderEntity
+manager: GameEntityManager
+--
+copy(): GameEntity
+get_component(): Component
+add_component(Component): void
+has_component(component_t): bool
+render_update(time_t, string): void
+
+
+
+ UMLClass
+
+ 510
+ 405
+ 210
+ 90
+
+
+**Component**
+
+
+
+ UMLClass
+
+ 510
+ 555
+ 255
+ 90
+
+
+**GameEntityManager**
+
+
+
+ UMLClass
+
+ 75
+ 765
+ 255
+ 90
+
+
+**WorldRenderEntity**
+
+
+
+ Relation
+
+ 180
+ 660
+ 45
+ 135
+
+ lt=<.
+ 10.0;70.0;10.0;10.0
+
+
+ Relation
+
+ 375
+ 585
+ 165
+ 45
+
+ lt=<.>
+ 90.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 375
+ 435
+ 165
+ 45
+
+ lt=<.
+ 90.0;10.0;10.0;10.0
+
+
+ UMLClass
+
+ 90
+ 0
+ 210
+ 90
+
+
+**GameEntity**
+
+
+
+ UMLClass
+
+ 375
+ 0
+ 210
+ 90
+
+
+**Component**
+
+
+
+ UMLClass
+
+ 90
+ 165
+ 255
+ 90
+
+
+**GameEntityManager**
+
+
+
+ Relation
+
+ 195
+ 75
+ 45
+ 120
+
+ lt=<.>
+ 10.0;60.0;10.0;10.0
+
+
+ Relation
+
+ 285
+ 30
+ 120
+ 45
+
+ lt=<.
+ 60.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 330
+ 195
+ 135
+ 45
+
+ lt=<.
+ 70.0;10.0;10.0;10.0
+
+
+ Text
+
+ 465
+ 195
+ 150
+ 105
+
+ /systems.../
+
+
+
+ UMLClass
+
+ 1290
+ 375
+ 210
+ 90
+
+ <<interface>>
+**Component**
+bg=pink
+
+
+
+ UMLClass
+
+ 1095
+ 525
+ 270
+ 90
+
+ <<interface>>
+**InternalComponent**
+bg=pink
+
+
+
+ UMLClass
+
+ 1425
+ 525
+ 255
+ 90
+
+ <<interface>>
+**APIComponent**
+bg=pink
+
+
+
+ Relation
+
+ 1320
+ 450
+ 45
+ 105
+
+ lt=<<-
+ 10.0;10.0;10.0;50.0
+
+
+ Relation
+
+ 1440
+ 450
+ 45
+ 105
+
+ lt=<<-
+ 10.0;10.0;10.0;50.0
+
+
+ UMLClass
+
+ 1065
+ 675
+ 210
+ 90
+
+
+**Activity**
+bg=green
+
+
+
+ UMLClass
+
+ 1065
+ 780
+ 210
+ 90
+
+
+**CommandQueue**
+bg=green
+
+
+
+ UMLClass
+
+ 1065
+ 885
+ 210
+ 90
+
+
+**Ownership**
+bg=green
+
+
+
+ UMLClass
+
+ 1065
+ 990
+ 210
+ 90
+
+
+**Position**
+bg=green
+
+
+
+ UMLClass
+
+ 1515
+ 675
+ 210
+ 90
+
+
+**Idle**
+bg=green
+
+
+
+ UMLClass
+
+ 1515
+ 780
+ 210
+ 90
+
+
+**Live**
+bg=green
+
+
+
+ UMLClass
+
+ 1515
+ 885
+ 210
+ 90
+
+
+**Move**
+bg=green
+
+
+
+ UMLClass
+
+ 1515
+ 990
+ 210
+ 90
+
+
+**Turn**
+bg=green
+
+
+
+ Relation
+
+ 1305
+ 600
+ 45
+ 465
+
+ lt=<<-
+ 10.0;10.0;10.0;290.0
+
+
+ Relation
+
+ 1455
+ 600
+ 45
+ 465
+
+ lt=<<-
+ 10.0;10.0;10.0;290.0
+
+
+ Relation
+
+ 1260
+ 705
+ 90
+ 45
+
+ lt=-
+ 40.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1260
+ 810
+ 90
+ 45
+
+ lt=-
+ 40.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1260
+ 915
+ 90
+ 45
+
+ lt=-
+ 40.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1260
+ 1020
+ 90
+ 45
+
+ lt=-
+ 40.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1455
+ 1020
+ 90
+ 45
+
+ lt=-
+ 40.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1455
+ 915
+ 90
+ 45
+
+ lt=-
+ 40.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1455
+ 810
+ 90
+ 45
+
+ lt=-
+ 40.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1455
+ 705
+ 90
+ 45
+
+ lt=-
+ 40.0;10.0;10.0;10.0
+
+
diff --git a/doc/code/images/primitive_curves_uml.svg b/doc/code/images/primitive_curves_uml.svg
new file mode 100644
index 0000000000..1ff0be8d94
--- /dev/null
+++ b/doc/code/images/primitive_curves_uml.svg
@@ -0,0 +1,171 @@
+
+
+
diff --git a/doc/code/images/segmented_curve.ggb b/doc/code/images/segmented_curve.ggb
new file mode 100644
index 0000000000..97beced399
Binary files /dev/null and b/doc/code/images/segmented_curve.ggb differ
diff --git a/doc/code/images/segmented_curve.png b/doc/code/images/segmented_curve.png
new file mode 100644
index 0000000000..5b8038af82
Binary files /dev/null and b/doc/code/images/segmented_curve.png differ
diff --git a/doc/code/images/simulation.uxf b/doc/code/images/simulation.uxf
new file mode 100644
index 0000000000..e7fc0893f7
--- /dev/null
+++ b/doc/code/images/simulation.uxf
@@ -0,0 +1,162 @@
+
+
+ 15
+
+ UMLClass
+
+ 1170
+ 525
+ 150
+ 90
+
+
+**Game**
+
+
+
+ UMLClass
+
+ 705
+ 495
+ 375
+ 285
+
+ **GameSimulation**
+--
+running: bool
+game: Game
+--
+set_modpacks(vector<string>): void
+init_event_handlers(): void
+run(): void
+start(): void
+stop(): void
+
+
+
+ UMLClass
+
+ 1170
+ 690
+ 180
+ 90
+
+
+**GameState**
+
+
+
+ UMLClass
+
+ 1440
+ 645
+ 195
+ 90
+
+
+**GameEntity**
+
+
+
+ UMLClass
+
+ 1440
+ 750
+ 165
+ 90
+
+
+**Terrain**
+
+
+
+ Relation
+
+ 1335
+ 690
+ 135
+ 45
+
+ lt=<.
+ 70.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1335
+ 750
+ 135
+ 45
+
+ lt=<.
+ 70.0;10.0;10.0;10.0
+
+
+ Relation
+
+ 1245
+ 600
+ 45
+ 120
+
+ lt=<.
+ 10.0;60.0;10.0;10.0
+
+
+ Relation
+
+ 1065
+ 555
+ 135
+ 45
+
+ lt=<.
+ 70.0;10.0;10.0;10.0
+
+
+ UMLClass
+
+ 705
+ 870
+ 150
+ 90
+
+
+**EventLoop**
+
+
+
+ UMLClass
+
+ 900
+ 870
+ 150
+ 90
+
+
+**TimeLoop**
+
+
+
+ Relation
+
+ 960
+ 765
+ 45
+ 135
+
+ lt=<.
+ 10.0;70.0;10.0;10.0
+
+
+ Relation
+
+ 765
+ 765
+ 45
+ 135
+
+ lt=<.
+ 10.0;70.0;10.0;10.0
+
+
diff --git a/doc/code/images/system_classes.uxf b/doc/code/images/system_classes.uxf
new file mode 100644
index 0000000000..7143f89e14
--- /dev/null
+++ b/doc/code/images/system_classes.uxf
@@ -0,0 +1,31 @@
+
+
+ 15
+
+ UMLClass
+
+ 795
+ 405
+ 510
+ 165
+
+ **Move**
+--
+move_default(GameEntity, phys3, time_t): time_t
+move_command(GameEntity, time_t): time_t
+
+
+
+ UMLClass
+
+ 810
+ 630
+ 375
+ 165
+
+ **Idle**
+--
+idle(GameEntity, time_t): time_t
+
+
+
diff --git a/doc/code/images/time_classes.uxf b/doc/code/images/time_classes.uxf
new file mode 100644
index 0000000000..55f0e4992c
--- /dev/null
+++ b/doc/code/images/time_classes.uxf
@@ -0,0 +1,76 @@
+
+
+ // Uncomment the following line to change the fontsize and font:
+fontsize=14
+// fontfamily=SansSerif //possible: SansSerif,Serif,Monospaced
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Welcome to UMLet!
+//
+// Double-click on elements to add them to the diagram, or to copy them
+// Edit elements by modifying the text in this panel
+// Hold Ctrl to select multiple elements
+// Use Ctrl+mouse to select via lasso
+//
+// Use +/- or Ctrl+mouse wheel to zoom
+// Drag a whole relation at its central square icon
+//
+// Press Ctrl+C to copy the whole diagram to the system clipboard (then just paste it to, eg, Word)
+// Edit the files in the "palettes" directory to create your own element palettes
+//
+// Select "Custom Elements > New..." to create new element types
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// This text will be stored with each diagram; use it for notes.
+ 15
+
+ UMLClass
+
+ 675
+ 300
+ 315
+ 180
+
+ **TimeLoop**
+--
+get_clock(): Clock
+run(): void
+start(): void
+stop(): void
+
+
+
+ UMLClass
+
+ 1095
+ 300
+ 315
+ 285
+
+ **Clock**
+--
+update_time(): void
+get_time(): time_t
+get_real_time(): time_t
+get_speed(): speed_t
+set_speed(speed_t): void
+start(): void
+stop(): void
+pause(): void
+resume(): void
+
+
+
+ Relation
+
+ 975
+ 375
+ 150
+ 45
+
+ lt=<.
+ 80.0;10.0;10.0;10.0
+
+
diff --git a/doc/code/images/time_classes_uml.svg b/doc/code/images/time_classes_uml.svg
new file mode 100644
index 0000000000..aed04e35b7
--- /dev/null
+++ b/doc/code/images/time_classes_uml.svg
@@ -0,0 +1,71 @@
+
+
+
diff --git a/doc/code/input/README.md b/doc/code/input/README.md
index d7c048a882..ab059a668f 100644
--- a/doc/code/input/README.md
+++ b/doc/code/input/README.md
@@ -4,6 +4,17 @@ Input managment concerns the handling external input sources that are used to in
The most common example for such input sources are probably **keyboard and mouse**, but other things like the **GUI**,
**networking** and **scripting** also fall under this umbrella.
+1. [Motivation](#motivation)
+2. [Basic Architecture](#basic-architecture)
+3. [Window System](#window-system)
+ 1. [Workflow](#workflow)
+ 2. [Class Relationships](#class-relationships)
+ 3. [Low-level Interface](#low-level-interface)
+ 4. [High-Level Interface (Controller)](#high-level-interface-controller)
+4. [Scripting](#scripting)
+5. [Network](#network)
+6. [AI](#ai)
+
## Motivation
diff --git a/doc/code/optimization.md b/doc/code/optimization.md
new file mode 100644
index 0000000000..36b3f03a22
--- /dev/null
+++ b/doc/code/optimization.md
@@ -0,0 +1,46 @@
+# Code Optimization
+
+openage should be efficient in its resource usage and offer high performance.
+As we are building a game engine, it is kind of expected that everything runs
+smoothly out-of-the-box.
+
+1. [Basic Rules](#basic-rules)
+2. [Checking Performance](#checking-performance)
+
+
+## Basic Rules
+
+Before anyone dives into the codebase and starts changing things around for better performance,
+they should consider these simple rules:
+
+- **readability comes first**: openage depends on outsiders being able to understand the engine code. If the code is super fast but totally incomprehensible, then nobody is going to maintain it.
+- **avoid premature optimization**: optimization without reason wastes time, reduces readability, and can sometimes lead to even worse performance. It's often better to implement a workable solution first and then use profiling to identify bottlenecks. Modern compilers are often smart enough to find small improvements during compilation anyway.
+- **prefer architecture improvements over speeding up specific use cases**: openage is a game *engine*, so we don't exactly know what people will use it for. We want the general case to be fast, not the edge cases.
+
+
+## Checking Performance
+
+Performance bottlenecks are best identified with a *profiler* of your choice.
+
+For **Python**, the built-in modules [cProfile](https://docs.python.org/3/library/profile.html)
+(speed) and [tracemalloc](https://docs.python.org/3/library/tracemalloc.html) (memory) are recommended.
+openage also provides convenience functions for them in its `openage.util.profiler` module.
+
+For **C++**, [valgrind](https://valgrind.org/info/tools.html) with `callgrind` (speed) and
+`memcheck` (memory) can be used. If you use valgrind, you should supply it the suppression
+file in our repo: [`etc/valgrind-python.supp`](/etc/valgrind-python.supp). This lets
+valgrind handle the Python parts of the engine a bit better.
+
+Any other profiler will also do the trick as long as you know how to use it (e.g. `perf`,
+`VTune`, `uProf`, etc.).
+
+Profiling a full engine run can be very resource intensive to profile. Therefore, it
+can be beneficial to profile engine [demos](/doc/code/testing.md#demos) for a specific
+subsystem, e.g. the renderer or the event system. This is also useful to
+check the performance of subsystems in isolation. However, consider that a full engine
+run may have different bottlenecks that are not obvious in the demos.
+
+Stresstests are demos that are specifically designed to test performance, particularly
+in the renderer subsystem. They can also include benchmarks with additional
+information, e.g. an FPS counter. When profiling performance critical code, implementing
+a stresstest should be considered to complement profiling results.
diff --git a/doc/code/pathfinding.md b/doc/code/pathfinding.md
deleted file mode 100644
index 9ed33e129c..0000000000
--- a/doc/code/pathfinding.md
+++ /dev/null
@@ -1,22 +0,0 @@
-Original algorithm
-------------------
-
-Short range pathfinder:
-Don't check all possible points and vectors.
-
-Path from (x0, y0) to (x1, y1):
-
-Draw line from (x0, y0)->(x1, y1).
-
-Query obstruction manager for collisions:
-Obstruction manager stores 2D outline of all relevant objects,
-dynamically adjusts outline sizes for the size of the moving unit.
-Just stores the area and shapes on the map occupied by everything.
-
-If obstruction manager detects no collision: unit walks the line.
-Else:
-find the 0. collision point (=c0), find way on outline around obstruction:
-branch path at c0 into 2 new ones in an `A*` like search tree.
-The new paths go to the each side of the obstruction,
-each one attempts to build a new path from their end to (x1, y1).
-For speedup: assemble multiple obstructions to convex hulls.
diff --git a/doc/code/renderer/level1.md b/doc/code/renderer/level1.md
index 9d163d3d69..8a7d4e1eb0 100644
--- a/doc/code/renderer/level1.md
+++ b/doc/code/renderer/level1.md
@@ -4,19 +4,19 @@ Low-level renderer for communicating with the OpenGL and Vulkan APIs.
## Overview
-1. [Level 1 Renderer](#level-1-renderer)
- 1. [Overview](#overview)
- 2. [Architecture](#architecture)
- 3. [Basic Usage](#basic--usage)
- 1. [Window/Renderer Creation](#windowrenderer-creation)
- 2. [Adding a Shader Program](#adding-a-shader-program)
- 3. [Creating a Renderable](#creating-a-renderable)
- 4. [Rendering and Displaying the Result](#rendering-and-displaying-the-result)
- 4. [Advanced Usage](#advanced-usage)
- 1. [Framebuffers / Multiple Render Passes](#framebuffers--multiple-render-passes)
- 2. [Complex Geometry](#complex-geometry)
- 3. [Uniform Buffers](#uniform-buffers)
- 5. [Thread-safety](#thread-safety)
+1. [Overview](#overview)
+2. [Architecture](#architecture)
+3. [Basic Usage](#basic--usage)
+ 1. [Window/Renderer Creation](#windowrenderer-creation)
+ 2. [Adding a Shader Program](#adding-a-shader-program)
+ 3. [Creating a Renderable](#creating-a-renderable)
+ 4. [Rendering and Displaying the Result](#rendering-and-displaying-the-result)
+4. [Advanced Usage](#advanced-usage)
+ 1. [Addressing Uniforms via numeric IDs](#addressing-uniforms-via-numeric-ids)
+ 2. [Framebuffers / Multiple Render Passes](#framebuffers--multiple-render-passes)
+ 3. [Complex Geometry](#complex-geometry)
+ 4. [Uniform Buffers](#uniform-buffers)
+5. [Thread-safety](#thread-safety)
## Architecture
@@ -46,7 +46,8 @@ abstract interface of the renderer renderer to make them usable with graphics ha
## Basic Usage
-- see demos in libopenage/renderer/demo
+Code examples can be found in the [renderer demos](/libopenage/renderer/demo/).
+See the [testing docs](/doc/code/testing.md#python-demos) on how to try them out.
### Window/Renderer Creation
@@ -142,10 +143,14 @@ std::shared_ptr input = shader_prog->new_uniform_input(
);
```
-Input values are passed to the method in pairs consisting of the uniform variable
-name (as a string) and the input value. The definition order doesn't matter and the
-method doesn't differentiate between different shader stages, so uniform inputs
-for vertex and fragment shaders can be freely mixed.
+Note that the definition order doesn't matter and the method doesn't differentiate
+between different shader stages, so uniform inputs for vertex and fragment shaders
+can be freely mixed.
+
+Input values are passed to the method in pairs consisting of the uniform ID and the
+input value. Uniform IDs can either be the uniform name from the shader source (as
+shown above) or a numeric ID that is determined at load time by the shader program.
+Numeric ID usage is explained in [this section](#addressing-uniforms-via-numeric-ids).
Uniform input values are automatically converted to the correct types expected by the uniform
definition, e.g. a `uint8_t` for a uniform with type `uint` will be transformed to the
@@ -209,6 +214,31 @@ window->update();
These are some of the more advanced features of the renderer.
+### Addressing Uniforms via numeric IDs
+
+Numeric uniform IDs are unique identifiers for a uniform in a shader program. They are
+assigned at load time and can be used to address uniforms instead of their string names.
+The type used for numeric IDs is `renderer::uniform_id_t`. The numeric ID of a uniform
+can be fetched from the shader program using the uniform name by calling the
+`renderer::ShaderProgram::get_uniform_id(..)` method.
+
+```c++
+uniform_id_t color_id = shader_prog->get_uniform_id("color");
+uniform_id_t time_id = shader_prog->get_uniform_id("time");
+uniform_id_t num_id = shader_prog->get_uniform_id("num");
+std::shared_ptr input = shader_prog->new_uniform_input(
+ color_id, Eigen::Vector3f{ 0.0f, 1.0f, 0.0f },
+ time_id, 0.0f,
+ num_id, 1337
+);
+```
+
+Setting uniform values via numeric IDs can be much faster than using strings as
+string lookups are avoided. This is especially useful for uniforms which are updated
+very frequently, e.g. every frame. However, this requires that the IDs are fetched
+at runtime and have to be stored somewhere.
+
+
### Framebuffers / Multiple Render Passes
Sometimes it is useful to render the scene in multiple passes, e.g. for post-processing
diff --git a/doc/code/terrain.md b/doc/code/terrain.md
deleted file mode 100644
index 974296dc68..0000000000
--- a/doc/code/terrain.md
+++ /dev/null
@@ -1,37 +0,0 @@
-terrain implementation
-======================
-
-
-if you ever asked yourself,
-"hey, myself, how is terrain stuff implemented in openage?",
-this document may enlighten your mind.
-
-
-ideas
------
-
-You might know the infinite terrain epicness of minecraft. That's exactly what we are doing in openage.
-
-The terrain consists of loads of small tiles, grouped in "chunks".
-
-One chunk contains an area of (currently) 16×16 tiles. Terrain is blended by the technique described in [doc/media/blendomatic.md](/doc/media/blendomatic.md).
-
-(short summary:
-a neighbor tile is blended onto the current one,
-if neighbor.priority > current.priority:
- the neighbor tile is drawn onto the current tile,
- but before that, parts are alphamasked.
-)
-
-
-master terrain
---------------
-
-this is the container of all chunks.
-
-
-
-chunk
------
-
-a grouped piece of terrain.
diff --git a/doc/code/testing.md b/doc/code/testing.md
index e23f4a366f..b64b51d9bb 100644
--- a/doc/code/testing.md
+++ b/doc/code/testing.md
@@ -15,9 +15,9 @@ Tests run without user interaction to check for errors automatically.
All tests are run automatically by [Kevin](https://github.com/SFTtech/kevin/) for pullrequests.
-You can invoke them with `./run test -a` or `make test`
+You can invoke them with `bin/run test -a` or `make test`
-Have a look at `./run test --help` for further options.
+Have a look at `bin/run test --help` for further options.
You are encouraged to write tests for all your contributions, as well as other components that currently lack testing.
@@ -35,7 +35,7 @@ They usually produce lots of output on stdout or may even be interactive. Python
All tests must be registered in `openage/testing/testlist.py` (else the game won't know about them).
-Also see `./run test --help`.
+Also see `bin/run test --help`.
## Adding new tests
@@ -135,7 +135,7 @@ C++ demos don't support `argv`; if you want that, make it a Python demo in a `.p
Similar to Python tests, but have one argument, `argv`. Pass arguments in the invocation:
- ./run test -d prime_demo 100
+ bin/run test -d prime_demo 100
Example demo:
diff --git a/doc/code/time.md b/doc/code/time.md
new file mode 100644
index 0000000000..fcbdde0b0f
--- /dev/null
+++ b/doc/code/time.md
@@ -0,0 +1,39 @@
+# Simulation Time
+
+*Simulation time* is the driving force behind pretty much all internal routines of the engine.
+Its used to schedule and execute events in the [event system](event_system.md),
+managing data of game entities at runtime via [curves](curves.md), and time
+animations in the [renderer](/doc/code/renderer/level2.md).
+
+To keep track of and synchronize time between different engine components, openage implements
+a low-level time control interface in the `openage::time` namespace. This system is very simple:
+Basically, it consists of a loop in a dedicated thread that constantly updates an internal clock.
+
+![time loop UML](images/time_classes_uml.svg)
+
+`TimeLoop` provides a basic interface to create an internal clock and run the update loop. Once
+initialized, the `Clock` instance can be accessed with the `get_clock()` method. For most calculations,
+the clock object is sufficient. Access to the time loop is only necessary if you want to
+pause/stop the entire simulation.
+
+openage represents time with the internal type `time::time_t` (which must not be confused with the C standard
+type `time_t`). `time::time_t` is a 64 bit signed fixed-point value (scaling factor 16)
+which represents the time passed in **seconds**, i.e. a value of `1.25` denotes 1.25 seconds or
+1250 milliseconds, respectively.
+
+`Clock` manages the current simulation time using the system clock. It also allows the configuration
+of a simulation speed (which can be negative). Time is advanced in each
+loop iteration of `TimeLoop` by calling the clock object's `update_time()` method. This method
+first calculates the real time difference between the previous and current update call. The result
+is then multiplied by the simulation speed and added to the total simulation time. Additionally, `Clock`
+keeps track of how much real time has passed, regardless of simulation speed. Real time is used
+in the renderer to time animation frames which should not affected by simulation speed.
+
+The time resolution used for the system clock is **milliseconds**. When requesting the current time
+from a clock via `get_time()` or `get_real_time()`, this gets converted into the openage `time::time_t`
+format.
+
+To prevent the clock updates from advancing too far if the program is stopped by the operating system
+or a debugger, `update_time()` sets a maximum limit on how much real time can pass between two update
+calls. This limit is currently hardcoded to 50ms. Similarly, there is a minimum limit of 1ms to
+prevent the time loop thread from stalling.
diff --git a/doc/contributing.md b/doc/contributing.md
index cdbbd6245a..6498b10f95 100644
--- a/doc/contributing.md
+++ b/doc/contributing.md
@@ -28,7 +28,7 @@ What's the concept?
We basically do whatever we think is good to do.
-The [openage architecture](/doc/architecture.md) is the foundation.
+The [openage architecture](/doc/code/architecture.md) is the foundation.
For our development concept, see the [development guide](/doc/development.md).
If you need some inspiration [what to work on, see above](#what-can-i-do).
@@ -81,15 +81,15 @@ For tiny stuff like typo fixes, just create your PR and be done with it.
- You can always update to upstream state by [rebasing](#rebasing).
- Discuss your ideas and your work:
- On the [group chats](/README.md#contact)
- - That way, "bad ideas" can be "resolved" beforehand and "better ideas" are found
+ - That way, "bad" ideas can be resolved beforehand and "better" ideas are found
- "Release early and often!" also applies to pull requests!
- - Once your branch has some visible work, create `[WIP]` pull request
+ - Once your branch has some visible work, create a draft/`[WIP]` pull request
- Give the pull request a description of what you did or want to do, so we can discuss it
- Make sure you are in the `copying.md` file
- People will be able to look at your code and give feedback
- You'll get free checks from the build bot
-- Once your work is done, remove the `[WIP]` so it can be merged
+- Once your work is done, mark it as ready for review/remove the `[WIP]` so it can be merged
- Do the changes that are requested by the reviewers.
- Aaaaaand you're done.
diff --git a/doc/convert/README.md b/doc/convert/README.md
index 8d97992042..1bf72efe36 100644
--- a/doc/convert/README.md
+++ b/doc/convert/README.md
@@ -1,5 +1,4 @@
-Convert Script
-==============
+# Convert Script
The convert script transforms media files to openage-compatible
formats, and generates code for parsing the generated files. It is written
@@ -13,92 +12,21 @@ so we need to convert the files to a format that is less special, and more
usable. This is what the convert script does.
-The convert script is divided into two parts:
-original media dependent and independent exports.
+The convert script is divided into two usage modes: *singlefile media conversion* and
+*modpack conversion*.
-Converting Media
-----------------
+## Singlefile Media Conversion
-* depends on existing original game installation
-* converts images, sounds, units and all other data values
- to compatible formats
-* creates files for the proprietary data package,
- its files will be used if free media files are missing.
+This converts graphics and sounds to open/compatible formats. Graphics are
+exported to PNG files. If the graphics file is an animation with multiple
+sprites, the converter will automatically pack all of them into a spritesheet.
+Sounds are exported to OPUS files.
-Exporting Structures
---------------------
+[See here](convert_single_file.md) for usage examples of the singlefile conversion.
-* totally independent of original game installation
-* exports original content structures
-* prevents redundancy:
- * data structures are needed for media export (to convert them)
- * data structures are needed to import them in the game
- * => structure definition at a single point.
-* generation is integrated into make system
-* generated files placed in `libopenage/gamedata/`
+## Modpack Conversion
-Conversion Procedure
-====================
-
-Media Files
------------
-
-A media file is a sound or image file. In the original media files, they are
-stored in [drs archives](/doc/media/drs-files.md).
-
-Each media file has an id and a file extension.
-Files can be "redefined" by overlay archives.
-
-All available archives have to be ordered, so that 'high priority' files
-override 'low priority' files. Mods have a higher priority than the base game
-to be able to apply their modifications.
-
-The overlay procedure works by assigning unique media files to the desired
-file source or archive.
-
-A dict has to be filled with values to assign the data source:
-
- (fileid, extension, destination) => (file archive)
-
-The resulting dict is a mapping between all available media files and the
-archive each should be obtained from.
-
-The files can then be converted to the appropriate format, the conversion
-procedure is selected by their file extension. Graphics are stored in the
-[SLP format](/doc/media/slp-files.md) and are converted to *png*. Sounds are
-*wav* files which are compressed to *opus*.
-
-
-Original metadata files
------------------------
-
-This data describes the configuration and behavior of the original game and its
-entities. Namely this defines the available buildings, units, their creation
-cost, etc.
-
-The [gamedata files](/doc/media/original_metadata.md) are parsed and converted to a
-huge tree of *csv* files. This is not optimal and will be improved soon(tm).
-
-
-### Data Updates ###
-
-To modify any data extracted from the original files in a hardcoded manner,
-simply update or delete values in the parsed python data structure. These
-modifications can be applied like this:
-
-```python
-def fix_data(data):
- # make lists for all units building locations
- for each unit:
- unit.building_loc = [unit.building_loc]
-
- huskarl_castle.building_loc.append(huskarl_barracks.building_loc)
- delete huskarl_barracks
-
- # value updating is done like this:
- get_unit(data, "hussar")["hp"] = 9001
- data.terrain.terrains[6].slp_id = 1337
-
- return data
-```
+Modpack conversion takes an original game installation and converts its data
+and media files into an [openage modpack](/doc/media/openage/modpacks.md).
+The converted game can then be run inside openage.
diff --git a/doc/convert/convert_single_file.md b/doc/convert/convert_single_file.md
index e998b7ad5d..f8c7f16d31 100644
--- a/doc/convert/convert_single_file.md
+++ b/doc/convert/convert_single_file.md
@@ -1,8 +1,7 @@
-Single file conversion
-======================
+# Singlefile conversion
-You can use openage to convert a single slp image from some drs archive to a
-png image.
+You can use openage to convert a single graphics file to PNG or sound file to OPUS. The
+asset can also be unpacked from a DRS archive.
The invocation could be:
@@ -16,6 +15,11 @@ Standalone SLPs (Age of Empires 1: DE and Age of Empires 2: HD):
python3 -m openage convert-file --palette-file ~/games/aoe2hd/Data/50500.bina 326.slp /tmp/rofl.png
```
+Standalone SLDs (Age of Empires 2: DE):
+```
+python3 -m openage convert-file --player-palette-file ~/games/aoe2de/Data/playercolor_blue.pal --palette-file ~/games/aoe2de/Data/b_west.pal u_elite_eagle.sld /tmp/rofl.png
+```
+
Standalone SMXs (Age of Empires 2: DE):
```
python3 -m openage convert-file --player-palette-file ~/games/aoe2de/Data/playercolor_blue.pal --palette-file ~/games/aoe2de/Data/b_west.pal u_elite_eagle.smx /tmp/rofl.png
@@ -26,6 +30,16 @@ Standalone SMPs (Age of Empires 2: DE):
python3 -m openage convert-file --player-palette-file ~/games/aoe2de/Data/playercolor_blue.pal --palette-file ~/games/aoe2de/Data/b_west.pal u_elite_eagle.smp /tmp/rofl.png
```
+WAVs in DRS archives (for older versions of Age of Empires 1, Age of Empires 2 and SWGB):
+```
+python3 -m openage convert-file --drs ~/games/aoe2/Data/sounds.drs 123.wav /tmp/rofl.opus
+```
+
+Standalone WAVs (Age of Empires 2: HD Edition):
+```
+python3 -m openage convert-file ~/games/aoe2/resources/123.wav /tmp/rofl.opus
+```
+
Have a look at `openage/convert/singlefile.py`, this is also a simple API demo
for how to interact with the aoe files.
diff --git a/doc/convert/nyan.md b/doc/convert/nyan.md
new file mode 100644
index 0000000000..5d28947c6a
--- /dev/null
+++ b/doc/convert/nyan.md
@@ -0,0 +1,9 @@
+# nyan data conversion
+
+We depend on the original game.
+We have a converter which spits out a files in our formats
+by parsing the original data.
+
+## Gamedata conversion
+
+`openage/convert/value_object/read/media/datfile/` specifies the format of the `.dat` from various games.
diff --git a/doc/debug.md b/doc/debug.md
index 01d3c0fac9..e8f838e0b2 100644
--- a/doc/debug.md
+++ b/doc/debug.md
@@ -1,6 +1,9 @@
# How to debug openage?
-## Qt Creator IDE
-See [qt_creator.md](/doc/ide/qt_creator.md)
+
+## Your favorite IDE
+
+Check the docs in [the `/doc/ide` subfolder](/doc/ide/).
+
## GDB
GDB can be used to debug C++ code in a terminal.
diff --git a/doc/development.md b/doc/development.md
index 99bc5650e0..05d5f2a26f 100644
--- a/doc/development.md
+++ b/doc/development.md
@@ -4,12 +4,11 @@ About development
Dev team
--------
-There is a "core dev team" (*mic_e*, *JJ*, *zuntrax*, but there's more)
-who work on the project in their free time,
+There is a "core dev team" who work on the project in their free time,
but since we all have other life-originating interrupts and fluctuating motivation,
our activity will alternate equally over time (just take a look at the commit statistics).
We "core devs" have known each other personally for a long time;
-thus, communication happens mostly over private channels (XMPP, Mumble, maybe even real life).
+thus, communication happens mostly over private channels (Matrix chats, Mumble, maybe even real life).
Development workflow
@@ -20,7 +19,7 @@ or an issue on the [issue tracker](https://github.com/sfttech/openage/issues).
The pull requests are then open for review by everybody.
If the pull request author thinks that their goals aren't met yet,
-it is tagged with `[WIP] $title`.
+they create it as a draft or tag it with `[WIP] $title`.
The author removes it once the goals are met.
In the big picture, we're working on completing those WIP pull requests,
@@ -32,7 +31,7 @@ just convenient to implement, or needed right now.
We're releasing early and often: whenever we add/improve a feature or
fix a bug and the code still compiles, we push the code to the github master.
Once significant changes have accumulated,
-we tag that commit with a new minor version.
+we tag that commit with a new version.
We don't have dedicated "subsystem maintainers" yet.
You can track down the person who did things with `git log` or `git blame`.
diff --git a/doc/ideas/ai.md b/doc/ideas/ai.md
index 991dbcb596..b7471a5a82 100644
--- a/doc/ideas/ai.md
+++ b/doc/ideas/ai.md
@@ -1,7 +1,102 @@
+# Artificial Intelligence
+
+## Interface
+
+This is a very early draft with some ideas taken
+from http://bwmirror.jurenka.sk/javadoc/bwapi/package-summary.html
+
+This file shall provide information about the AI interface design.
+
+
+### General ideas
+
+ * An AI registers hooks for desired events
+ * The C++ engine triggers python AI functions when desired events occur
+ * The AI then can trigger actions on the controllable units
+ * Periodically (1 Hz?), the AI gets ticked to allow event independent decisions
+ * Get the AI its own dedicated thread
+
+
+### Event types
+
+Python interface basic events:
+
+ * on_start(GameInfo info)
+ * on_frame(Game game)
+ * on_end(Player winner)
+
+Some AIs may prefer an event driven approach (rule based AI):
+
+ * on_unit_discover(Unit unit)
+ * on_unit_complete(Unit unit)
+ * on_unit_lost(Unit unit)
+ * many more...
+
+
+Some AIs will need to simulate ticks into the future to help
+make decisions for the current tick. (min-max, MCTS, etc.)
+
+
+### Relevant structures for the AI
+
+Remember: These are just some ideas for possible interfaces,
+they are not existent in the game yet.
+
+```cpp
+struct game_info {
+ int num_players;
+ int map_size;
+ int pop_limit;
+ resources_type;
+ map_view;
+ starting_age;
+ victory_mode;
+}
+```
+
+```cpp
+class Game {
+ Player[] allies(); // all the ally players that have not left or been defeated.
+ bool can_build_at(Unit builder, TilePosition position, UnitType type);
+ bool can_create(Unit builder, UnitType type);
+ bool can_research(Unit unit, TechType type);
+ bool can_upgrade(Unit unit, UpgradeType type);
+ Player[] enemies(); // all the enemy players that have not left or been defeated.
+ Unit[] get_all_units(); // returns all the visible units.
+
+ // many more examples at: http://bwmirror.jurenka.sk/javadoc/bwapi/Game.html
+}
+```
+
+```cpp
+class Player {
+ string get_name(); // returns the name of the player.
+ Faction get_faction(); // returns the faction of the player.
+ Unit[] get_units(); // returns the set of units the player own.
+ bool has_researched(TechType tech);
+ int get_resource() // Returns the amount of resources the player owns.
+ // many more...
+}
+```
+
+```cpp
+class Unit {
+ // used to get information about individual units as well as issue orders to units
+}
+```
+
+```cpp
+class Calculation {
+ // used to get computation heavy calculations from the C++ engine
+}
+```
+
+
+## Gameplay
+
Improvements for the AI's gameplay
-Villagers
----------
+### Villagers
- Don't collect resources in hazardous areas / send the military to clear them out
- Send military task forces to guard strategic resources / tower them
@@ -10,8 +105,7 @@ Villagers
- Prefer to build castles and towers near the water/thin places
- Create more trade cogs and trade ships
-Military
---------
+### Military
- Avoid fighting in hazardous areas / prefer fighting in range of own castles
- Coordinates reaction to attacks on units (during march, ...) (including attacks by towers/castles)
@@ -23,7 +117,6 @@ Military
- send smaller amount of troops earlier in the game to annoy you
- while building up a big army in the back
-Ship
---------
+### Ship
- Transport ship can deliver villagers to another islands
diff --git a/doc/media/openage/modpack_definition_file.md b/doc/media/openage/modpack_definition_file.md
index c4549f4cc9..a9ff6d6a41 100644
--- a/doc/media/openage/modpack_definition_file.md
+++ b/doc/media/openage/modpack_definition_file.md
@@ -1,6 +1,6 @@
# Modpack Definition File
-**Format Version:** 1
+**Format Version:** 2
The modpack definition file is the header file for [modpacks](modpacks.md).
@@ -22,54 +22,55 @@ find this specification document.
The following parameters have to be specified.
-Parameter | Data Type | Optional | Description
----------------------|-----------|----------|------------
-`file_version` | String | No | Version of the modpack definition file format.
+| Parameter | Data Type | Optional | Description |
+| -------------- | --------- | -------- | ---------------------------------------------- |
+| `file_version` | String | No | Version of the modpack definition file format. |
## [info] Section
`[info]` contains general information about the modpack.
-Parameter | Data Type | Optional | Description
--------------------|---------------|----------|------------
-`packagename` | String | No | Name of the modpack.
-`version` | String | No | Version number.
-`repo` | String | Yes | Name of the repo where the package is hosted.
-`alias` | String | Yes | Alias of the modpack. Aliases can be used for replacing other modpacks.
-`title` | String | Yes | Title used in UI.
-`description` | String | Yes | Path to a file with a short description (max 500 chars).
-`long_description` | String | Yes | Path to a file with a detailed description.
-`url` | String | Yes | Link to the modpack's website.
-`license` | Array[String] | Yes | License(s) of the modpack.
+| Parameter | Data Type | Optional | Description |
+| ------------------ | ------------- | -------- | ------------------------------------------------------------------------ |
+| `packagename` | String | No | Name of the modpack. |
+| `version` | String | No | Internal version number. Must have [semver](https://semver.org/) format. |
+| `versionstr` | String | Yes | Human-readable version string. |
+| `repo` | String | Yes | Name of the repo where the package is hosted. |
+| `alias` | String | Yes | Alias of the modpack. Aliases can be used for replacing other modpacks. |
+| `title` | String | Yes | Title used in UI. |
+| `description` | String | Yes | Path to a file with a short description (max 500 chars). |
+| `long_description` | String | Yes | Path to a file with a detailed description. |
+| `url` | String | Yes | Link to the modpack's website. |
+| `license` | Array[String] | Yes | License(s) of the modpack. |
## [assets] Section
`[assets]` contains paths to assets in the modpack.
-Parameter | Data Type | Optional | Description
----------------|----------------|----------|------------
-`include` | Array[String] | No | List of paths to assets that should be mounted on load time. Paths are allowed to contain wildcards.
-`exclude` | Array[String] | Yes | List of paths to assets that should be excluded from mounting. Paths are allowed to contain wildcards.
+| Parameter | Data Type | Optional | Description |
+| --------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------ |
+| `include` | Array[String] | No | List of paths to assets that should be mounted on load time. Paths are allowed to contain wildcards. |
+| `exclude` | Array[String] | Yes | List of paths to assets that should be excluded from mounting. Paths are allowed to contain wildcards. |
## [dependency] Section
`[dependency]` contains a list of other modpacks that the modpack depends on.
-Parameter | Data Type | Optional | Description
----------------|----------------|----------|------------
-`modpacks` | Array[String] | Yes | List of modpack aliases or identifiers.
+| Parameter | Data Type | Optional | Description |
+| ---------- | ------------- | -------- | --------------------------------------- |
+| `modpacks` | Array[String] | Yes | List of modpack aliases or identifiers. |
## [conflict] Section
`[conflict]` contains a list of other modpacks that the modpack conflicts with.
-Parameter | Data Type | Optional | Description
----------------|----------------|----------|------------
-`modpacks` | Array[String] | Yes | List of modpack aliases or identifiers.
+| Parameter | Data Type | Optional | Description |
+| ---------- | ------------- | -------- | --------------------------------------- |
+| `modpacks` | Array[String] | Yes | List of modpack aliases or identifiers. |
## [authors] Section
@@ -78,29 +79,29 @@ Parameter | Data Type | Optional | Description
Every author must have their own subtable `[authors.{authorname}]`. The
subtable can set the following parameters.
-Parameter | Data Type | Optional | Description
------------|---------------|----------|------------
-`name` | String | No | Nickname of the author. Must be unique for the modpack.
-`fullname` | String | Yes | Full name of the author.
-`since` | String | Yes | Version number of the release where the author started to contribute.
-`until` | String | Yes | Version number of the release where the author stopped to contribute.
-`roles` | Array[String] | Yes | List of roles of the author during the creation of the modpack.
-`contact` | Table | Yes | Contact information (see below).
+| Parameter | Data Type | Optional | Description |
+| ---------- | ------------- | -------- | --------------------------------------------------------------------- |
+| `name` | String | No | Nickname of the author. Must be unique for the modpack. |
+| `fullname` | String | Yes | Full name of the author. |
+| `since` | String | Yes | Version number of the release where the author started to contribute. |
+| `until` | String | Yes | Version number of the release where the author stopped to contribute. |
+| `roles` | Array[String] | Yes | List of roles of the author during the creation of the modpack. |
+| `contact` | Table | Yes | Contact information (see below). |
The contact table can use the following parameters.
-Parameter | Data Type | Optional | Description
------------|---------------|----------|------------
-`discord` | String | Yes | Discord username.
-`email` | String | Yes | Email address.
-`github` | String | Yes | GitHub username.
-`gitlab` | String | Yes | Gitlab username.
-`irc` | String | Yes | IRC username.
-`mastodon` | String | Yes | Mastodon username.
-`matrix` | String | Yes | Matrix username.
-`reddit` | String | Yes | Reddit username.
-`twitter` | String | Yes | Twitter username.
-`youtube` | String | Yes | YouTube username.
+| Parameter | Data Type | Optional | Description |
+| ---------- | --------- | -------- | ------------------ |
+| `discord` | String | Yes | Discord username. |
+| `email` | String | Yes | Email address. |
+| `github` | String | Yes | GitHub username. |
+| `gitlab` | String | Yes | Gitlab username. |
+| `irc` | String | Yes | IRC username. |
+| `mastodon` | String | Yes | Mastodon username. |
+| `matrix` | String | Yes | Matrix username. |
+| `reddit` | String | Yes | Reddit username. |
+| `twitter` | String | Yes | Twitter username. |
+| `youtube` | String | Yes | YouTube username. |
## [authorgroups] Section
@@ -110,8 +111,8 @@ It can be used in addition to `[authors]` to signify that the modpack is
a team effort.
-Parameter | Data Type | Optional | Description
---------------|---------------|----------|------------
-`name` | String | No | Group or team name.
-`authors` | Array[String] | No | List of author identifiers. These must match up with subtable keys in the `[authors]` section, e.g. `"xxbunny123"` references `[authors.xxbunny123]`.
-`description` | String | Yes | Path to a file with a description of the team.
+| Parameter | Data Type | Optional | Description |
+| ------------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `name` | String | No | Group or team name. |
+| `authors` | Array[String] | No | List of author identifiers. These must match up with subtable keys in the `[authors]` section, e.g. `"xxbunny123"` references `[authors.xxbunny123]`. |
+| `description` | String | Yes | Path to a file with a description of the team. |
diff --git a/doc/media/openage/modpacks.md b/doc/media/openage/modpacks.md
index 4bbf9c580e..f475a6a636 100644
--- a/doc/media/openage/modpacks.md
+++ b/doc/media/openage/modpacks.md
@@ -13,7 +13,7 @@ anything from minor enhancements to standalone games.
Apart from game-related data, modpacks store configuration data for loading
them into the engine. Most of this data is located in the
-**[modpack definition file](###modpack-definition-file)**. Modpacks can define
+**[modpack definition file](#modpack-definition-file)**. Modpacks can define
their interaction with other modpacks by specifying **dependencies** and
**conflicts**.
diff --git a/doc/media/original-metadata.md b/doc/media/original-metadata.md
index 318630fa34..5e5540fe44 100644
--- a/doc/media/original-metadata.md
+++ b/doc/media/original-metadata.md
@@ -5,7 +5,7 @@ All data relevant for the game (e.g. how much costs building the castle?
What cultures exist? Can my priest overclock his "Wololo?")
are stored in a binary format in the file `empires2_x1_p1.dat`.
-The format is described in the [huge struct definition](/openage/convert/gamedata/empiresdat.py).
+The format is described in the [huge struct definition](/openage/convert/value_object/read/media/datfile/empiresdat.py).
These data files are basically a huge tree structure.
diff --git a/doc/media_convert.md b/doc/media_convert.md
index 04f72554b1..a6526de99e 100644
--- a/doc/media_convert.md
+++ b/doc/media_convert.md
@@ -8,12 +8,9 @@ Currently we *support* conversion for these games:
* **RoR**: Age of Empires 1 (1997) + Rise of Rome
* **AoC**: Age of Empires 2 (1999) + The Conqueror's
* **SWGB**: Star Wars: Galactic Battlegrounds + Clone Campaigns
-* **DE2**: Age of Empires 2: Definitve Edition
-
-The following versions are currently *unsupported*, but will be added later:
-
* **HD**: Age of Empires 2 (2013) (formerly: Age of Empires 2: HD Edition)
* **DE1**: Age of Empires 1: Definitive Edition
+* **DE2**: Age of Empires 2: Definitve Edition
On Linux, you may have to use additional measures to download DE2 versions from Steam:
@@ -26,7 +23,7 @@ On Linux, you may have to use additional measures to download DE2 versions from
`./steamcmd.sh +@sSteamCmdForcePlatformType windows +login USERNAME +force_install_dir ASSET_DIR +app_update GAME_APP_ID validate +quit`
The game version vs Game_ID table such as below:
- Age of Empires(2013): 221380
+ Age of Empires (2013): 221380
Age of Empires: Definitive Edition: 1017900
Age of Empires II (Rise of the Rajas): 488060
Age of Empires II: Definitive Edition: 813780
@@ -44,4 +41,10 @@ The game will ask for your AoE II installation folder; examples include:
~/.steam/steam/SteamApps/common/Age2HD
~/Library/Application Support/Steam/steamapps/common/Age2HD
+Alternatively, if your game installation is not found, you can trigger the conversion manually:
+
+```
+python3 -m openage convert --force --source-dir /path/to/game/install
+```
+
You will find the converted files in `assets/converted`.
diff --git a/doc/milestones.md b/doc/milestones.md
deleted file mode 100644
index f0c66f094d..0000000000
--- a/doc/milestones.md
+++ /dev/null
@@ -1,32 +0,0 @@
-Milestones
-==========
-
-
-- [x] 0: decide on the technology and architecture we're using.
- * language, libraries, build system, coding standards, ...
- * may be incomplete, will be changed later on anyway
-- [x] 1: Create a database format for game data ([nyan](https://github.com/SFTtech/nyan))
-- [x] 2: Renderer using OpenGL and Vulkan ([code doc](https://github.com/SFTtech/openage/tree/master/doc/code/renderer))
-- [x] 3: Event system for the game simulation
-- [x] 4: Design a modding API for the engine ([reference sheet](https://github.com/SFTtech/openage/tree/master/doc/nyan/api_reference))
-- [X] 5: Convert AoE2 game data to our engine formats ([code doc](https://github.com/SFTtech/openage/tree/master/doc/code/converter))
-- [ ] 6: Gamestate calculation with curves logic ([blogpost](https://blog.openage.dev/t1-curves-logic.html))
-- [ ] 7: Gameplay simulation! Where everything comes together
-- [ ] 8: Multiplayer
-
-things to be done sometime:
-
-* game recording, playback
-* lobby + lobby server
-* implement AI Python API (use GlaDOS?)
-* messaging and taunts (important!)
-* random map generation (RMS scripts?)
-* age2 map reader
- * scenario reader
- * maybe even the singleplayer campaigns
-
-things to keep in mind:
-
-* performance (responsiveness, fps, needed resources)
-* high-quality code
-* having fun!
diff --git a/doc/nyan/nyan.md b/doc/nyan/README.md
similarity index 75%
rename from doc/nyan/nyan.md
rename to doc/nyan/README.md
index 28fa0a5aaf..dcada2c2f6 100644
--- a/doc/nyan/nyan.md
+++ b/doc/nyan/README.md
@@ -1,13 +1,11 @@
-nyan
-====
+# nyan
nyan - yet another notation
https://github.com/SFTtech/nyan
-Idea
-----
+## Idea
nyan is the data storage format for openage.
@@ -32,18 +30,23 @@ it can actually store unit upgrades, unit abilities, research,
civ bonuses and age upgrades.
-Technical specification
------------------------
+## Technical specification
-[nyan specification](https://github.com/SFTtech/nyan/blob/master/doc/nyan.md)
+[nyan specification](https://github.com/SFTtech/nyan/blob/master/doc/README.md)
-openage specifics
------------------
+## openage specifics
nyan is a general purpose data language,
the following describes the openage specific usage of it.
* [data conversion](conversion.md)
* [openage engine nyan interface](openage-lib.md)
-* [examples](examples.md)
+
+## Examples
+
+For the beginning, you can look at these resources to find examples of the nyan notation:
+
+* [nyan docs](https://github.com/SFTtech/nyan/blob/master/doc/nyan.md)
+* converted game assets from [our converter](/doc/convert/)
+* [openage modding guide for nyan](https://github.com/SFTtech/openage-modding/tree/master/tutorials/nyan)
diff --git a/doc/nyan/conversion.md b/doc/nyan/conversion.md
deleted file mode 100644
index b094c0f1fb..0000000000
--- a/doc/nyan/conversion.md
+++ /dev/null
@@ -1,20 +0,0 @@
-nyan data conversion
-====================
-
-We depend on the original game.
-We have a converter which spits out a files in our formats
-by parsing the original data.
-
-Gamedata conversion
--------------------
-
-* `openage/convert/gamedata/` specifies the format of `empires_x1_p1.dat`
- * this format allows reading the binary file and storing it into tables
- * these tables and the original format suck,
- information is redundant and we can heavily improve it
- * buuut: the file format is given (every original game has it.)
- * :arrow_right: data transformation.
-* the original file will be read according to the original format (we can't help it)
-* a transformation is specified
- * mapping: original format -> our awesome format
- * redundancy is purged (e.g. no more double huskarl unit (1x castle, 1x barracs))
diff --git a/doc/nyan/examples.md b/doc/nyan/examples.md
deleted file mode 100644
index a4b5d02d76..0000000000
--- a/doc/nyan/examples.md
+++ /dev/null
@@ -1,9 +0,0 @@
-openage nyan examples
-=====================
-
-openage will make excessive use of the nyan data storage.
-
-This file will help you understand how you'd create nyan files
-to modify or extend the behavior of openage.
-
-For the beginning, you can look [here](https://github.com/SFTtech/nyan/blob/master/doc/nyan.md) for examples.
diff --git a/doc/nyan/openage-lib.md b/doc/nyan/openage-lib.md
index 69f7efda99..751dd7fe97 100644
--- a/doc/nyan/openage-lib.md
+++ b/doc/nyan/openage-lib.md
@@ -1,13 +1,22 @@
-openage nyan library
-====================
+# openage nyan library
The openage engine defines all kinds of `NyanObject`s so the nyan interpreter can verify data integrity. Together, the defined objects provide the API that can be used by the games running on the engine.
This interface will be described here.
+1. [Design principles](#design-principles)
+2. [API objects](#api-objects)
+ 1. [Entity](#entity)
+ 2. [GameEntity](#gameentity)
+ 3. [Ability](#ability)
+ 4. [Modifier](#modifier)
+ 5. [Effect](#effect)
+3. [C++ Interface](#c-interface)
+
+
## Design principles
-In addition to the [nyan design goals](https://github.com/SFTtech/nyan/blob/master/doc/nyan.md#design-goals), the openage mod API follows its own design principles. It is recommended to follow these principles when extending the API.
+In addition to the [nyan design goals](https://github.com/SFTtech/nyan/blob/master/doc/README.md#design-idea), the openage mod API follows its own design principles. It is recommended to follow these principles when extending the API.
**Single API tree**
@@ -80,3 +89,8 @@ By default, modifiers apply to the abilities of the game entity that stores them
There are two major types of effects: Discrete and continuous. Discrete effects happen at a *specific point in time*, while continuous effects are applied *over time* at a per second rate. Any discrete effect can be combined with every other discrete effect. The same applies for continuous effects.
For an effect to apply, the effector's `Effect` needs a corresponding `Resistance` object on the resistor's side. Otherwise the effect is not evaluated.
+
+## C++ Interface
+
+openage provides a thin nyan API layer to make interacing with nyan more accessible. This layer is implemented in `libopenage/gamestate/api` and
+covers the most common use caes for retrieving objects and reading values. Using/extending the nyan API layer should be preferred when adding game logic that accesses nyan to the engine.
diff --git a/doc/project_structure.md b/doc/project_structure.md
index 945ef5c3b9..4b2498aae8 100644
--- a/doc/project_structure.md
+++ b/doc/project_structure.md
@@ -1,6 +1,4 @@
-Project Structure
-=================
-
+# Project Structure
One of the biggest problems for newcomers who want to contribute code to free
software projects is that they have no idea where to start.
@@ -9,21 +7,36 @@ Reading, understanding and finding the relevant code part is hard.
This file explains the modular structure of the project.
+1. [Architecture](#architecture)
+2. [Languages](#languages)
+3. [Folders](#folders)
+ 1. [assets/](#assets)
+ 2. [buildsystem/](#buildsystem)
+ 3. [cfg/](#cfg)
+ 4. [dist/](#dist)
+ 5. [doc/](#doc)
+ 6. [etc/](#etc)
+ 7. [legal/](#legal)
+ 8. [libopenage/](#libopenage)
+ 9. [openage/](#openage)
+ 10. [packaging/](#packaging)
+
+
## Architecture
-The [overall architecture](/doc/architecture.md) describes the conceptual overview.
+The [overall architecture](/doc/code/architecture.md) describes the conceptual overview.
## Languages
We use Python, Cython and C++.
-Extension | Language | Usage
---------------|-----------|---------
-`.py` | Python | Everything that does not crunch data
-`.pyx` `.pxd` | Cython | Fast Python code, glue between C/C++ and Python
-`.h` `.cpp` | C++ | Data crunching code: simulation, graphics, sound, ...
+| Extension | Language | Usage |
+| ------------- | -------- | ----------------------------------------------------- |
+| `.py` | Python | Everything that does not crunch data |
+| `.pyx` `.pxd` | Cython | Fast Python code, glue between C/C++ and Python |
+| `.h` `.cpp` | C++ | Data crunching code: simulation, graphics, sound, ... |
## Folders
@@ -37,8 +50,8 @@ of the project.
Game assets required at run time are placed in here. This includes everything
that is converted from the original assets (see [asset conversion](media_convert.md))
-and other input like shaders etc. The directory is installed to `/usr/share/openage/assets`
-or whatever the platform recommends.
+and other input like shaders etc. The directory is `assets` for local builds or
+installed to `/usr/share/openage/assets` or whatever the platform recommends.
### buildsystem/
@@ -68,6 +81,10 @@ The code itself is commented with `/** doxygen comments */`.
See [doc/README.md](/doc/README.md) for documentation guidelines.
+### etc/
+
+Additional resources for development tools, e.g. pylint and valgrind configs.
+
### legal/
diff --git a/doc/releasing.md b/doc/releasing.md
index 23c834aa67..8a3f37f955 100644
--- a/doc/releasing.md
+++ b/doc/releasing.md
@@ -1,15 +1,15 @@
# Releasing
-* Bump the version number in `openage_version`. Given `0.y.z`, both `0.y.(z+1)` and `0.(y+1).0` are valid. The latter will probably be more common, but which of those you choose doesn't really matter.
+openage uses [Semver](https://semver.org).. People often seem to overlook the special rules for initial development versions. Please avoid prerelease versions (`-alpha`, `-beta`, `-rc.1`, ...) for now. We might decide to include build metadata like date or commit hash, that's up for discussion.
- Read up on [Semver](https://semver.org) for details. People often seem to overlook the special rules for initial development versions. Please avoid prerelease versions (`-alpha`, `-beta`, `-rc.1`, ...) for now. We might decide to include build metadata like date or commit hash, that's up for discussion.
-* Create a new changelog named `doc/changelogs/engine/$VERSION.md`.
-* Commit, PR, merge, pull as usual.
-* OPTIONAL: Set up [commit signing](https://help.github.com/en/articles/managing-commit-signature-verification), signing tags will make a "verified" badge show up on the release later.
-* Tag the merge commit, something like `git tag -s v0.5.0`. The prefixed `v` is mandatory.
-* Push the tag to GitHub.
-* Use the GitHub web interface to publish a release from the tag. Make it look nice, see previous release description. If in doubt, save the draft and have someone else proof read it. Also include the changelog.
-* In the future, kevin will automatically attach build artifacts like installers to the release. For now, you will need to do that manually. Have fun setting up a windows build environment or if you feel like being lazy, go bug someone who already has. Actually, taking care of where to build for windows before publishing the release might not be a bad idea.
-* OPTIONAL: Brag on social media. /r/openage, /r/aoe2 and AoE2 Discords might be the right place to do so.
+Release guide:
-If you need help or are not completely sure what you are doing, ask in [`#sfttech:matrix.org`](https://app.element.io/#/room/#sfttech:matrix.org) and highlight `zuntrax` or someone else who has done it before. Better having someone help you than screwing up a release.
+1. Create a new changelog named `doc/changelogs/engine/$VERSION.md`.
+1. Bump the version number in `openage_version`. Given `0.y.z`, both `0.y.(z+1)` and `0.(y+1).0` are valid. The latter will probably be more common, but which of those you choose doesn't really matter.
+1. Commit, PR, merge, pull as usual.
+ * OPTIONAL: Set up [commit signing](https://help.github.com/en/articles/managing-commit-signature-verification), signing tags will make a "verified" badge show up on the release later.
+1. Tag the merge commit, something like `git tag -s v0.5.0`. The prefixed `v` is mandatory.
+1. Push the tag to GitHub.
+1. Use the GitHub web interface to publish a release from the tag. Make it look nice, see previous release description. If in doubt, save the draft and have someone else proof read it. Also include the changelog.
+1. In the future, kevin will automatically attach build artifacts like installers to the release. For now, you will need to do that manually. Have fun setting up a windows build environment or if you feel like being lazy, go bug someone who already has. Actually, taking care of where to build for windows before publishing the release might not be a bad idea.
+ * OPTIONAL: Brag on social media. /r/openage, /r/aoe2 and AoE2 Discords might be the right place to do so.
diff --git a/doc/reverse_engineering/game_mechanics/formations.md b/doc/reverse_engineering/game_mechanics/formations.md
index 86d4fd2443..dd39371ccd 100644
--- a/doc/reverse_engineering/game_mechanics/formations.md
+++ b/doc/reverse_engineering/game_mechanics/formations.md
@@ -89,7 +89,7 @@ For a start one has to look at the behavior of an individual subformation. Let's
............
```
-The number of lines (or `row_count`) in this subformation is always determined by the number of its units. Unit width and different unit types have no influence here. The `row_count` can be calculated by an algorithm ([see here](https://gist.github.com/Piruzzolo/e744c9da5ed18898fae85f29a560e90f)). After the value has been calculated the units will be sorted into the lines starting from the top left.
+The number of lines (or `row_count`) in this subformation is always determined by the number of its units. Unit width and different unit types have no influence here. The `row_count` can be calculated by an algorithm. After the value has been calculated the units will be sorted into the lines starting from the top left.
```
............
diff --git a/doc/reverse_engineering/game_mechanics/swgb/shields.md b/doc/reverse_engineering/game_mechanics/swgb/shields.md
index 962182271f..2ab7a4d570 100644
--- a/doc/reverse_engineering/game_mechanics/swgb/shields.md
+++ b/doc/reverse_engineering/game_mechanics/swgb/shields.md
@@ -12,7 +12,7 @@ A shielded unit gradually fills a shield bar (originally yellow and placed
under the health bar), that doubles its health points. Aditionally, shielded
units have a yellow glow effect, and [the UI](user-interface.md#cap) lights the
leftmost indicator in yellow when a shielded unit is selected, as visible
-[here]([https://www.youtube.com/watch?v=pwAtMv_eiM4&feature=youtu.be&t=1786]).
+[here](https://www.youtube.com/watch?v=pwAtMv_eiM4&t=1786).
Shields are depleted first when taking hits, before the regular health bar.
Regenerating shields do not affect the health bar.
diff --git a/doc/reverse_engineering/networking/01-general.md b/doc/reverse_engineering/networking/01-general.md
index 4ac6cc1fa7..e171eb4703 100644
--- a/doc/reverse_engineering/networking/01-general.md
+++ b/doc/reverse_engineering/networking/01-general.md
@@ -25,59 +25,59 @@ Sync packets can be further categorized into periodic (sent in regular intervals
Packets are recognized by a one byte long "command byte" in the header. So far 12 different network commands have been identified.
-Command | Purpose
---------|--------------
-0x31 | Sync
-0x32 | Sync
-0x35 | Sync (Lobby)
-0x3e | Player-issued
-0x41 | Sync
-0x43 | Chat Message
-0x44 | Sync
-0x4d | Sync
-0x51 | De-Sync
-0x52 | Readying (Lobby)
-0x53 | Sync
-0x5a | Lobby
+| Command | Purpose |
+| ------- | ---------------- |
+| 0x31 | Sync |
+| 0x32 | Sync |
+| 0x35 | Sync (Lobby) |
+| 0x3e | Player-issued |
+| 0x41 | Sync |
+| 0x43 | Chat Message |
+| 0x44 | Sync |
+| 0x4d | Sync |
+| 0x51 | De-Sync |
+| 0x52 | Readying (Lobby) |
+| 0x53 | Sync |
+| 0x5a | Lobby |
All packets with command `0x3e` have a second "command byte" after the header that represents the command a player has given ingame. To avoid confusion, we will call all player-issued commands "actions" and reserve the term "commands" for the actual network commands seen above. Furthermore, the identifier for a player's action will be called "action byte". 34 of these can be found in network packets.
-Action | Purpose
--------|------------
-0x00 | Primary Action (Attacking, Resource gathering, Boarding Transport Ship)
-0x01 | Stop
-0x02 | Primary Action (AI)
-0x03 | Move
-0x0a | Move (AI)
-0x0b | Resign
-0x10 | Set waypoint
-0x12 | Stance
-0x13 | Guard
-0x14 | Follow
-0x15 | Patrol
-0x17 | Formation
-0x1b | Save & Exit
-0x1f | Coordinated Move (AI)
-0x64 | Train unit (AI)
-0x65 | Research
-0x66 | Build
-0x67 | Diplomacy/Cheats/Change Speed
-0x69 | Build wall
-0x6a | Delete
-0x6b | Attack ground
-0x6c | Tribute
-0x6e | Repair
-0x6f | Unboard/Ungarrison
-0x72 | Toggle gate
-0x73 | Flare
-0x75 | Garrison/Stop building unit
-0x77 | Train unit (Human)
-0x78 | Rally point
-0x7a | Sell
-0x7b | Buy
-0x7e | Drop relic
-0x7f | Toggle townbell
-0x80 | Back to work
+| Action | Purpose |
+| ------ | ----------------------------------------------------------------------- |
+| 0x00 | Primary Action (Attacking, Resource gathering, Boarding Transport Ship) |
+| 0x01 | Stop |
+| 0x02 | Primary Action (AI) |
+| 0x03 | Move |
+| 0x0a | Move (AI) |
+| 0x0b | Resign |
+| 0x10 | Set waypoint |
+| 0x12 | Stance |
+| 0x13 | Guard |
+| 0x14 | Follow |
+| 0x15 | Patrol |
+| 0x17 | Formation |
+| 0x1b | Save & Exit |
+| 0x1f | Coordinated Move (AI) |
+| 0x64 | Train unit (AI) |
+| 0x65 | Research |
+| 0x66 | Build |
+| 0x67 | Diplomacy/Cheats/Change Speed |
+| 0x69 | Build wall |
+| 0x6a | Delete |
+| 0x6b | Attack ground |
+| 0x6c | Tribute |
+| 0x6e | Repair |
+| 0x6f | Unboard/Ungarrison |
+| 0x72 | Toggle gate |
+| 0x73 | Flare |
+| 0x75 | Garrison/Stop building unit |
+| 0x77 | Train unit (Human) |
+| 0x78 | Rally point |
+| 0x7a | Sell |
+| 0x7b | Buy |
+| 0x7e | Drop relic |
+| 0x7f | Toggle townbell |
+| 0x80 | Back to work |
When the game is recorded, the UDP data stream of a `0x3e` packet (without the header) is written straight into the .mgx files with few changes. Viewing the recording will therefore simulate the exact actions that were done by the players. For more information on this, check the **Further Reading** section below.
@@ -89,12 +89,12 @@ Values in the network protocol can have a length of one, two or four byte. Littl
The data is described with few data types, which are shown in the table below.
-Length | Data Types
----------|------------
-1 byte | int8, (ASCII) char
-2 byte | int16
-4 byte | int32, float
-other | (1-dimensional) array
+| Length | Data Types |
+| ------ | --------------------- |
+| 1 byte | int8, (ASCII) char |
+| 2 byte | int16 |
+| 4 byte | int32, float |
+| other | (1-dimensional) array |
Most of the fields present in the network protocol have a fixed length. The use cases for variable length fields are usually lists of `unit_id`s or waypoints and will be handled as arrays in this documentation.
@@ -113,10 +113,10 @@ In this document, we will assume that AoC uses a carthesian coordinate system wi
To get a better understanding of the networking design and the underlying principles, it might be beneficial to read these sources.
[1] Dave Pottering, *1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond*, 2001
-https://www.gamasutra.com/view/feature/131503/1500_archers_on_a_288_network_.php?page=1
+https://www.gamedeveloper.com/programming/1500-archers-on-a-28-8-network-programming-in-age-of-empires-and-beyond
[2] Matthew Pritchard, *How to Hurt the Hackers: The Scoop on Internet Cheating and How You Can Combat It*, 2000
-https://www.gamasutra.com/view/feature/3149/how_to_hurt_the_hackers_the_scoop_.php
+https://www.gamedeveloper.com/design/how-to-hurt-the-hackers-the-scoop-on-internet-cheating-and-how-you-can-combat-it
[3] Stefan Kolb, *Age of Empires 2: The Conquerors — Savegame File Format Specification*,
https://github.com/stefan-kolb/aoc-mgx-format
diff --git a/doc/running.md b/doc/running.md
new file mode 100644
index 0000000000..ffcdf15e76
--- /dev/null
+++ b/doc/running.md
@@ -0,0 +1,121 @@
+# How to run openage?
+
+This document explains the different run modes in openage.
+
+1. [Quickstart](#quickstart)
+2. [Modes](#modes)
+ 1. [`game`](#game)
+ 2. [`main`](#main)
+ 3. [`test`](#test)
+ 4. [`convert`](#convert)
+ 5. [`convert-file`](#convert-file)
+ 6. [`convert-export-api`](#convert-export-api)
+ 7. [`codegen`](#codegen)
+
+
+## Quickstart
+
+After building the project with the commands
+
+```
+./configure
+make
+```
+
+you can execute
+
+```
+bin/run
+```
+
+from the same subfolder. This automatically selects the [`game` mode](#game) as default
+to start a new game instance and also creates all necessary configs.
+
+If prompts appear, follow the instructions and choose what you think is best. It's
+almost idiot-proof!
+
+
+## Modes
+
+Modes can be selected manually by appending the `bin/run` prompt with the mode name.
+
+### `game`
+
+```
+bin/run game
+```
+
+Start the engine and immediately create a new game instance. This run mode is supposed
+to jump straight into a game (or replay recording).
+
+If no converted modpacks can be found, this mode will start with a prompt asking if
+the user wants to convert any before initializing the game.
+
+It's the default run mode.
+
+
+### `main`
+
+```
+bin/run main
+```
+
+This run mode is supposed to start a main menu or launcher which allows configuring a
+game. Neither of these are implemented at the moment, so `main` just does the same
+thing as `game`.
+
+
+### `test`
+
+```
+bin/run test
+```
+
+Used for running [tests and engine demos](code/testing.md). These show off selected
+subsystems of the engine.
+
+
+### `convert`
+
+```
+bin/run convert
+```
+
+Runs the [asset conversion](media_convert.md) subsystem which creates openage modpacks
+from original game installations.
+
+
+### `convert-file`
+
+```
+bin/run convert-file
+```
+
+Allows converting single media files using the original game formats to open formats
+(PNG for graphics, OPUS for sounds).
+
+
+### `convert-export-api`
+
+```
+bin/run convert-export-api
+```
+
+Exports the [openage modding API](nyan/README.md) nyan objects to a modpack that can
+be loaded by the engine.
+
+This is supposed to be temporary solution until the modding API is finalized. Currently,
+the modding API nyan objects are hardcoded into the converter. In the future, the
+modding API modpack should be part of the engine config which is loaded by both the engine
+the converter subsystem as a single source of truth.
+
+
+### `codegen`
+
+```
+bin/run codegen
+```
+
+Runs the code generation logic for the build process. In particular, this generates code
+for [mako](https://www.makotemplates.org/) templates and creates the test lists for the
+[testing](code/testing.md) subsystem.
diff --git a/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt
index dafe823c2e..f1772bfc36 100644
--- a/libopenage/CMakeLists.txt
+++ b/libopenage/CMakeLists.txt
@@ -364,6 +364,7 @@ add_subdirectory("rng")
add_subdirectory("shader")
add_subdirectory("terrain")
add_subdirectory("testing")
+add_subdirectory("time")
add_subdirectory("unit")
add_subdirectory("util")
add_subdirectory("versions")
diff --git a/libopenage/assets/mod_manager.h b/libopenage/assets/mod_manager.h
index 6d7ec5277b..b049603f83 100644
--- a/libopenage/assets/mod_manager.h
+++ b/libopenage/assets/mod_manager.h
@@ -5,8 +5,11 @@
#include
#include
#include
+#include
+#include "error/error.h"
#include "log/log.h"
+#include "log/message.h"
#include "assets/modpack.h"
#include "util/path.h"
diff --git a/libopenage/assets/modpack.cpp b/libopenage/assets/modpack.cpp
index 8d784ef92e..acc3865696 100644
--- a/libopenage/assets/modpack.cpp
+++ b/libopenage/assets/modpack.cpp
@@ -33,6 +33,9 @@ ModpackInfo parse_modepack_def(const util::Path &info_file) {
}
// optionals
+ if (info.contains("versionstr")) {
+ def.versionstr = info.at("versionstr").as_string();
+ }
if (info.contains("repo")) {
def.repo = info.at("repo").as_string();
}
diff --git a/libopenage/assets/modpack.h b/libopenage/assets/modpack.h
index 96ee08fa29..7c858b5ed7 100644
--- a/libopenage/assets/modpack.h
+++ b/libopenage/assets/modpack.h
@@ -43,6 +43,7 @@ struct ModpackInfo {
// see /doc/media/openage/modpack_definition_file.md
std::string id;
std::string version;
+ std::string versionstr;
std::string repo;
std::string alias;
std::string title;
diff --git a/libopenage/coord/phys.cpp b/libopenage/coord/phys.cpp
index 9f6ccacd3c..bb5e89498d 100644
--- a/libopenage/coord/phys.cpp
+++ b/libopenage/coord/phys.cpp
@@ -8,6 +8,7 @@
#include "coord/tile.h"
#include "terrain/terrain.h"
#include "util/math.h"
+#include "util/math_constants.h"
namespace openage::coord {
@@ -38,7 +39,7 @@ phys_angle_t phys2_delta::to_angle(const coord::phys2_delta &other) const {
auto det = other.ne.to_float() * this->se.to_float() - this->ne.to_float() * other.se.to_float();
auto dot = this->ne.to_float() * other.ne.to_float() + this->se.to_float() * other.se.to_float();
- auto angle = std::atan2(det, dot) * 180 / std::numbers::pi;
+ auto angle = std::atan2(det, dot) * 180 / math::PI;
if (angle < 0) {
angle += 360;
}
@@ -99,7 +100,7 @@ phys_angle_t phys3_delta::to_angle(const coord::phys2_delta &other) const {
auto det = other.ne.to_float() * this->se.to_float() - this->ne.to_float() * other.se.to_float();
auto dot = this->ne.to_float() * other.ne.to_float() + this->se.to_float() * other.se.to_float();
- auto angle = std::atan2(det, dot) * 180 / std::numbers::pi;
+ auto angle = std::atan2(det, dot) * 180 / math::PI;
if (angle < 0) {
angle += 360;
}
diff --git a/libopenage/coord/scene.cpp b/libopenage/coord/scene.cpp
index 7ea121750d..d34519bfe4 100644
--- a/libopenage/coord/scene.cpp
+++ b/libopenage/coord/scene.cpp
@@ -8,6 +8,7 @@
#include "coord/pixel.h"
#include "coord/tile.h"
#include "util/math.h"
+#include "util/math_constants.h"
namespace openage::coord {
@@ -53,7 +54,7 @@ float scene2_delta::to_angle(const coord::scene2_delta &other) const {
auto det = other.ne.to_float() * this->se.to_float() - this->ne.to_float() * other.se.to_float();
auto dot = this->ne.to_float() * other.ne.to_float() + this->se.to_float() * other.se.to_float();
- auto angle = std::atan2(det, dot) * 180 / std::numbers::pi;
+ auto angle = std::atan2(det, dot) * 180 / math::PI;
if (angle < 0) {
angle += 360;
}
diff --git a/libopenage/curve/CMakeLists.txt b/libopenage/curve/CMakeLists.txt
index 8554b14f39..1aa1efb55b 100644
--- a/libopenage/curve/CMakeLists.txt
+++ b/libopenage/curve/CMakeLists.txt
@@ -1,7 +1,6 @@
add_sources(libopenage
base_curve.cpp
continuous.cpp
- curve.cpp
discrete.cpp
discrete_mod.cpp
interpolated.cpp
diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h
index 5534cf43dc..8c56586637 100644
--- a/libopenage/curve/base_curve.h
+++ b/libopenage/curve/base_curve.h
@@ -2,14 +2,30 @@
#pragma once
+#include
+#include
+#include
+#include
#include
+#include
+#include
+
+#include "error/error.h"
+#include "log/log.h"
+#include "log/message.h"
#include "curve/keyframe_container.h"
-#include "event/event_loop.h"
#include "event/evententity.h"
+#include "time/time.h"
+#include "util/fixed_point.h"
+
+namespace openage {
+namespace event {
+class EventLoop;
+}
-namespace openage::curve {
+namespace curve {
template
class BaseCurve : public event::EventEntity {
@@ -35,9 +51,9 @@ class BaseCurve : public event::EventEntity {
BaseCurve(BaseCurve &&) = default;
- virtual T get(const time_t &t) const = 0;
+ virtual T get(const time::time_t &t) const = 0;
- virtual T operator()(const time_t &now) {
+ virtual T operator()(const time::time_t &now) {
return get(now);
}
@@ -46,14 +62,14 @@ class BaseCurve : public event::EventEntity {
*
* @return Keyframe time and value.
*/
- virtual std::pair frame(const time_t &time) const;
+ virtual std::pair frame(const time::time_t &time) const;
/**
* Get the closest keyframe with t > \p time.
*
* @return Keyframe time and value.
*/
- virtual std::pair next_frame(const time_t &time) const;
+ virtual std::pair next_frame(const time::time_t &time) const;
/**
* Insert/overwrite given value at given time and erase all elements
@@ -61,26 +77,26 @@ class BaseCurve : public event::EventEntity {
* If multiple elements exist at the given time,
* overwrite the last one.
*/
- virtual void set_last(const time_t &at, const T &value);
+ virtual void set_last(const time::time_t &at, const T &value);
/**
* Insert a value at the given time.
* If there already is a value at this time,
* the value is inserted directly after the existing one.
*/
- virtual void set_insert(const time_t &at, const T &value);
+ virtual void set_insert(const time::time_t &at, const T &value);
/**
* Insert a value at the given time.
* If there already is a value at this time,
* the given value will replace the first value with the same time.
*/
- virtual void set_replace(const time_t &at, const T &value);
+ virtual void set_replace(const time::time_t &at, const T &value);
/**
* Remove all values that have the given time.
*/
- virtual void erase(const time_t &at);
+ virtual void erase(const time::time_t &at);
/**
* Integrity check, for debugging/testing reasons only.
@@ -99,7 +115,7 @@ class BaseCurve : public event::EventEntity {
* the keyframes of \p other.
*/
void sync(const BaseCurve &other,
- const time_t &start = std::numeric_limits::min());
+ const time::time_t &start = std::numeric_limits::min());
/**
* Copy keyframes from another curve (with a different element type) to this curve.
@@ -118,7 +134,7 @@ class BaseCurve : public event::EventEntity {
template
void sync(const BaseCurve &other,
const std::function &converter,
- const time_t &start = std::numeric_limits::min());
+ const time::time_t &start = std::numeric_limits::min());
/**
* Get the identifier of this curve.
@@ -184,7 +200,7 @@ class BaseCurve : public event::EventEntity {
template
-void BaseCurve::set_last(const time_t &at, const T &value) {
+void BaseCurve::set_last(const time::time_t &at, const T &value) {
auto hint = this->container.last(at, this->last_element);
// erase max one same-time value
@@ -202,7 +218,7 @@ void BaseCurve::set_last(const time_t &at, const T &value) {
template
-void BaseCurve::set_insert(const time_t &at, const T &value) {
+void BaseCurve::set_insert(const time::time_t &at, const T &value) {
auto hint = this->container.insert_after(at, value, this->last_element);
// check if this is now the final keyframe
if (hint->time > this->last_element->time) {
@@ -213,28 +229,28 @@ void BaseCurve::set_insert(const time_t &at, const T &value) {
template
-void BaseCurve::set_replace(const time_t &at, const T &value) {
+void BaseCurve::set_replace(const time::time_t &at, const T &value) {
this->container.insert_overwrite(at, value, this->last_element);
this->changes(at);
}
template
-void BaseCurve::erase(const time_t &at) {
+void BaseCurve::erase(const time::time_t &at) {
this->last_element = this->container.erase(at, this->last_element);
this->changes(at);
}
template
-std::pair BaseCurve::frame(const time_t &time) const {
+std::pair BaseCurve::frame(const time::time_t &time) const {
auto e = this->container.last(time, this->container.end());
return std::make_pair(e->time, e->value);
}
template
-std::pair BaseCurve::next_frame(const time_t &time) const {
+std::pair BaseCurve::next_frame(const time::time_t &time) const {
auto e = this->container.last(time, this->container.end());
e++;
return std::make_pair(e->time, e->value);
@@ -254,10 +270,10 @@ std::string BaseCurve::str() const {
template
void BaseCurve::check_integrity() const {
- time_t last_time = std::numeric_limits::min();
+ time::time_t last_time = std::numeric_limits::min();
for (const auto &keyframe : this->container) {
if (keyframe.time < last_time) {
- throw Error{ERR << "curve is broken after t=" << last_time << ": " << this->str()};
+ throw Error{MSG(err) << "curve is broken after t=" << last_time << ": " << this->str()};
}
last_time = keyframe.time;
}
@@ -265,9 +281,9 @@ void BaseCurve::check_integrity() const {
template
void BaseCurve::sync(const BaseCurve &other,
- const time_t &start) {
+ const time::time_t &start) {
// Copy keyframes between containers for t >= start
- this->last_element = this->container.sync_after(other.container, start);
+ this->last_element = this->container.sync(other.container, start);
// Check if this->get() returns the same value as other->get() for t = start
// If not, insert a new keyframe at start
@@ -284,9 +300,9 @@ template
template
void BaseCurve::sync(const BaseCurve &other,
const std::function &converter,
- const time_t &start) {
+ const time::time_t &start) {
// Copy keyframes between containers for t >= start
- this->last_element = this->container.sync_after(other.get_container(), converter, start);
+ this->last_element = this->container.sync(other.get_container(), converter, start);
// Check if this->get() returns the same value as other->get() for t = start
// If not, insert a new keyframe at start
@@ -298,4 +314,5 @@ void BaseCurve::sync(const BaseCurve &other,
this->changes(start);
}
-} // namespace openage::curve
+} // namespace curve
+} // namespace openage
diff --git a/libopenage/curve/continuous.h b/libopenage/curve/continuous.h
index 0d32f14629..3eb7170ddb 100644
--- a/libopenage/curve/continuous.h
+++ b/libopenage/curve/continuous.h
@@ -1,12 +1,13 @@
-// Copyright 2017-2019 the openage authors. See copying.md for legal info.
+// Copyright 2017-2023 the openage authors. See copying.md for legal info.
#pragma once
-#include
#include
+#include
+
+#include "curve/interpolated.h"
+#include "time/time.h"
-#include "interpolated.h"
-#include "../log/log.h"
namespace openage::curve {
@@ -19,9 +20,9 @@ namespace openage::curve {
* interpolation.
*
* The bound template type T has to implement `operator+(T)` and
- * `operator*(time_t)`.
+ * `operator*(time::time_t)`.
*/
-template
+template
class Continuous : public Interpolated {
public:
using Interpolated::Interpolated;
@@ -32,10 +33,10 @@ class Continuous : public Interpolated {
* If multiple elements exist at the given time,
* overwrite all of them.
*/
- void set_last(const time_t &t, const T &value) override;
+ void set_last(const time::time_t &t, const T &value) override;
/** This just calls set_replace in order to guarantee the continuity. */
- void set_insert(const time_t &t, const T &value) override;
+ void set_insert(const time::time_t &t, const T &value) override;
/** human readable identifier */
std::string idstr() const override;
@@ -43,7 +44,7 @@ class Continuous : public Interpolated {
template
-void Continuous::set_last(const time_t &at, const T &value) {
+void Continuous::set_last(const time::time_t &at, const T &value) {
auto hint = this->container.last(at, this->last_element);
// erase all same-time entries
@@ -61,7 +62,7 @@ void Continuous::set_last(const time_t &at, const T &value) {
template
-void Continuous::set_insert(const time_t &t, const T &value) {
+void Continuous::set_insert(const time::time_t &t, const T &value) {
this->set_replace(t, value);
}
@@ -81,4 +82,4 @@ std::string Continuous::idstr() const {
}
-} // openage::curve
+} // namespace openage::curve
diff --git a/libopenage/curve/curve.cpp b/libopenage/curve/curve.cpp
deleted file mode 100644
index 4ec84fe407..0000000000
--- a/libopenage/curve/curve.cpp
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2017-2018 the openage authors. See copying.md for legal info.
-
-#include "curve.h"
-
-namespace openage::curve {
-
-// This file is intended to be empty
-
-} // openage::curve
diff --git a/libopenage/curve/discrete.h b/libopenage/curve/discrete.h
index 085f85b0fd..cdcb3daa31 100644
--- a/libopenage/curve/discrete.h
+++ b/libopenage/curve/discrete.h
@@ -4,9 +4,12 @@
#include
#include
+#include
+#include
#include
-#include "base_curve.h"
+#include "curve/base_curve.h"
+#include "time/time.h"
namespace openage::curve {
@@ -29,7 +32,7 @@ class Discrete : public BaseCurve {
* Does not interpolate anything,
* just returns gives the raw value of the last keyframe with time <= t.
*/
- T get(const time_t &t) const override;
+ T get(const time::time_t &t) const override;
/**
* Get a human readable id string.
@@ -39,17 +42,17 @@ class Discrete : public BaseCurve {
/**
* Return the last time and keyframe with time <= t.
*/
- std::pair get_time(const time_t &t) const;
+ std::pair get_time(const time::time_t &t) const;
/**
* Return, if existing, the time and value of keyframe with time < t
*/
- std::optional> get_previous(const time_t &t) const;
+ std::optional> get_previous(const time::time_t &t) const;
};
template
-T Discrete::get(const time_t &time) const {
+T Discrete::get(const time::time_t &time) const {
auto e = this->container.last(time, this->last_element);
this->last_element = e; // TODO if Caching?
return e->value;
@@ -72,7 +75,7 @@ std::string Discrete::idstr() const {
template
-std::pair Discrete::get_time(const time_t &time) const {
+std::pair Discrete::get_time(const time::time_t &time) const {
auto e = this->container.last(time, this->last_element);
this->last_element = e;
return std::make_pair(e->time, e->value);
@@ -80,7 +83,7 @@ std::pair Discrete::get_time(const time_t &time) const {
template
-std::optional> Discrete::get_previous(const time_t &time) const {
+std::optional> Discrete::get_previous(const time::time_t &time) const {
auto e = this->container.last(time, this->last_element);
this->last_element = e;
diff --git a/libopenage/curve/discrete_mod.h b/libopenage/curve/discrete_mod.h
index ef16b23088..a641541688 100644
--- a/libopenage/curve/discrete_mod.h
+++ b/libopenage/curve/discrete_mod.h
@@ -2,7 +2,16 @@
#pragma once
+#include
+#include
+#include
+#include
+#include
+
+#include "curve/base_curve.h"
#include "curve/discrete.h"
+#include "time/time.h"
+#include "util/fixed_point.h"
namespace openage::curve {
@@ -30,9 +39,9 @@ class DiscreteMod : public Discrete {
// Override insertion/erasure to get interval time
- void set_last(const time_t &at, const T &value) override;
- void set_insert(const time_t &at, const T &value) override;
- void erase(const time_t &at) override;
+ void set_last(const time::time_t &at, const T &value) override;
+ void set_insert(const time::time_t &at, const T &value) override;
+ void erase(const time::time_t &at) override;
/**
* Get a human readable id string.
@@ -42,35 +51,35 @@ class DiscreteMod : public Discrete {
/**
* Get the raw value of the last keyframe with time <= (t - start) % interval_length.
*/
- T get_mod(const time_t &t, const time_t &start) const;
+ T get_mod(const time::time_t &t, const time::time_t &start) const;
/**
* Get the last time and keyframe with time <= (t - start) % interval_length.
*/
- std::pair get_time_mod(const time_t &t, const time_t &start) const;
+ std::pair get_time_mod(const time::time_t &t, const time::time_t &start) const;
/**
* Get, if existing, the time and value of keyframe with time < (t - start) % interval_length.
*/
- std::optional> get_previous_mod(const time_t &t, const time_t &start) const;
+ std::optional> get_previous_mod(const time::time_t &t, const time::time_t &start) const;
private:
/**
* Length of the time interval of this curve (time between first and last keyframe).
*/
- time_t time_length;
+ time::time_t time_length;
};
template
-void DiscreteMod::set_last(const time_t &at, const T &value) {
+void DiscreteMod::set_last(const time::time_t &at, const T &value) {
BaseCurve::set_last(at, value);
this->time_length = at;
}
template
-void DiscreteMod::set_insert(const time_t &at, const T &value) {
+void DiscreteMod::set_insert(const time::time_t &at, const T &value) {
BaseCurve::set_insert(at, value);
if (this->time_length < at) {
@@ -80,7 +89,7 @@ void DiscreteMod::set_insert(const time_t &at, const T &value) {
template
-void DiscreteMod::erase(const time_t &at) {
+void DiscreteMod::erase(const time::time_t &at) {
BaseCurve::erase(at);
if (this->time_length == at) {
@@ -105,40 +114,40 @@ std::string DiscreteMod::idstr() const {
template
-T DiscreteMod::get_mod(const time_t &time, const time_t &start) const {
- time_t offset = time - start;
+T DiscreteMod::get_mod(const time::time_t &time, const time::time_t &start) const {
+ time::time_t offset = time - start;
if (this->time_length == 0) {
// modulo would fail here so return early
return Discrete::get(0);
}
- time_t mod = offset % this->time_length;
+ time::time_t mod = offset % this->time_length;
return Discrete::get(mod);
}
template
-std::pair DiscreteMod::get_time_mod(const time_t &time, const time_t &start) const {
- time_t offset = time - start;
+std::pair DiscreteMod::get_time_mod(const time::time_t &time, const time::time_t &start) const {
+ time::time_t offset = time - start;
if (this->time_length == 0) {
// modulo would fail here so return early
return Discrete::get_time(0);
}
- time_t mod = offset % this->time_length;
+ time::time_t mod = offset % this->time_length;
return Discrete::get_time(mod);
}
template
-std::optional> DiscreteMod::get_previous_mod(const time_t &time, const time_t &start) const {
- time_t offset = time - start;
+std::optional> DiscreteMod::get_previous_mod(const time::time_t &time, const time::time_t &start) const {
+ time::time_t offset = time - start;
if (this->time_length == 0) {
// modulo would fail here so return early
return Discrete::get_previous(0);
}
- time_t mod = offset % this->time_length;
+ time::time_t mod = offset % this->time_length;
return Discrete::get_previous(mod);
}
diff --git a/libopenage/curve/interpolated.h b/libopenage/curve/interpolated.h
index bdb1fe1743..49a189a124 100644
--- a/libopenage/curve/interpolated.h
+++ b/libopenage/curve/interpolated.h
@@ -2,10 +2,10 @@
#pragma once
-#include
+#include "curve/base_curve.h"
+#include "time/time.h"
+#include "util/fixed_point.h"
-#include "../log/log.h"
-#include "base_curve.h"
namespace openage::curve {
@@ -15,7 +15,7 @@ namespace openage::curve {
* Extends the value container to support interpolation between values.
*
* The bound template type T has to implement `operator +(T)` and
- * `operator *(time_t)`.
+ * `operator *(time::time_t)`.
*/
template
class Interpolated : public BaseCurve {
@@ -34,19 +34,19 @@ class Interpolated : public BaseCurve {
* val([a:x, b:y], t) = x + (y - x)/(b - a) * (t - a)
*/
- T get(const time_t &) const override;
+ T get(const time::time_t &) const override;
};
template
-T Interpolated::get(const time_t &time) const {
+T Interpolated::get(const time::time_t &time) const {
const auto &e = this->container.last(time, this->last_element);
this->last_element = e;
auto nxt = e;
++nxt;
- time_t interval = 0;
+ time::time_t interval = 0;
auto offset = time - e->time;
diff --git a/libopenage/curve/iterator.h b/libopenage/curve/iterator.h
index b965b65640..2500e083cd 100644
--- a/libopenage/curve/iterator.h
+++ b/libopenage/curve/iterator.h
@@ -1,8 +1,10 @@
-// Copyright 2017-2018 the openage authors. See copying.md for legal info.
+// Copyright 2017-2023 the openage authors. See copying.md for legal info.
#pragma once
-#include "curve.h"
+#include "time/time.h"
+#include "util/fixed_point.h"
+
namespace openage::curve {
@@ -11,7 +13,7 @@ namespace openage::curve {
*/
template
+ typename iterator_t = typename container_t::const_iterator>
class CurveIterator {
public:
/**
@@ -28,12 +30,11 @@ class CurveIterator {
/**
* The iterator needs a reference to the container
*/
- explicit CurveIterator(const container_t *c)
- :
+ explicit CurveIterator(const container_t *c) :
base{},
container{c},
- from{-std::numeric_limits::max()},
- to{+std::numeric_limits::max()} {}
+ from{-std::numeric_limits::max()},
+ to{+std::numeric_limits::max()} {}
protected:
/**
@@ -41,9 +42,8 @@ class CurveIterator {
*/
CurveIterator(const iterator_t &base,
const container_t *container,
- const time_t &from,
- const time_t &to)
- :
+ const time::time_t &from,
+ const time::time_t &to) :
base{base},
container{container},
from{from},
@@ -51,21 +51,21 @@ class CurveIterator {
public:
/** Default copy c'tor */
- CurveIterator (const CurveIterator &) = default;
+ CurveIterator(const CurveIterator &) = default;
virtual ~CurveIterator() = default;
/** Default assignment operator */
- CurveIterator &operator= (
+ CurveIterator &operator=(
const CurveIterator &) = default;
/** Dereference will call the virtual function */
- virtual const val_t &operator *() const {
+ virtual const val_t &operator*() const {
return this->value();
}
/** Dereference will call the virutal function */
- virtual const val_t *operator ->() const {
+ virtual const val_t *operator->() const {
return &this->value();
}
@@ -73,7 +73,7 @@ class CurveIterator {
* For equalness only the base iterator will be testet - not the timespans
* this is defined in.
*/
- virtual bool operator ==(const CurveIterator &rhs) const {
+ virtual bool operator==(const CurveIterator &rhs) const {
return this->base == rhs.base;
}
@@ -81,17 +81,18 @@ class CurveIterator {
* For unequalness only the base iterator will be testet - not the timespans
* this is defined in.
*/
- virtual bool operator !=(const CurveIterator &rhs) const {
+ virtual bool operator!=(const CurveIterator &rhs) const {
return this->base != rhs.base;
}
/**
* Advance to the next valid element.
*/
- virtual CurveIterator &operator ++() {
+ virtual CurveIterator &operator++() {
do {
++this->base;
- } while (this->container->end().base != this->base and not this->valid());
+ }
+ while (this->container->end().base != this->base and not this->valid());
return *this;
}
@@ -106,14 +107,14 @@ class CurveIterator {
/**
* Access the lower end value of the defined time frame
*/
- const time_t &get_from() const {
+ const time::time_t &get_from() const {
return from;
}
/**
* Access the higher end value of the defined time frame
*/
- const time_t &get_to() const {
+ const time::time_t &get_to() const {
return to;
}
@@ -125,11 +126,11 @@ class CurveIterator {
const container_t *container;
/// The time, from where this iterator started to iterate.
- time_t from;
+ time::time_t from;
/// The time, to where this iterator will iterate.
- time_t to;
+ time::time_t to;
};
-} // openage::curve
+} // namespace openage::curve
diff --git a/libopenage/curve/keyframe.h b/libopenage/curve/keyframe.h
index a0fc39d1e6..d6b90af4b5 100644
--- a/libopenage/curve/keyframe.h
+++ b/libopenage/curve/keyframe.h
@@ -1,10 +1,10 @@
-// Copyright 2019-2019 the openage authors. See copying.md for legal info.
+// Copyright 2019-2023 the openage authors. See copying.md for legal info.
#pragma once
-#include
+#include "time/time.h"
+#include "util/fixed_point.h"
-#include "curve.h"
namespace openage::curve {
@@ -23,18 +23,18 @@ class Keyframe {
/**
* New, default-constructed element at the given time
*/
- Keyframe(const time_t &time) :
+ Keyframe(const time::time_t &time) :
time{time} {}
/**
* New element fron time and value
*/
- Keyframe(const time_t &time, const T &value) :
+ Keyframe(const time::time_t &time, const T &value) :
time{time},
value{value} {}
- const time_t time = std::numeric_limits::min();
+ const time::time_t time = std::numeric_limits::min();
T value = T{};
};
-} // openage::curve
+} // namespace openage::curve
diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h
index e025de1a4d..1d7079498b 100644
--- a/libopenage/curve/keyframe_container.h
+++ b/libopenage/curve/keyframe_container.h
@@ -2,14 +2,15 @@
#pragma once
+#include
+#include
#include
-#include
#include
-#include "curve/curve.h"
#include "curve/keyframe.h"
-#include "error/error.h"
-#include "util/compiler.h"
+#include "time/time.h"
+#include "util/fixed_point.h"
+
namespace openage::curve {
@@ -78,7 +79,7 @@ class KeyframeContainer {
* Get the last element in the curve which is at or before the given time.
* (i.e. elem->time <= time). Given a hint where to start the search.
*/
- iterator last(const time_t &time,
+ iterator last(const time::time_t &time,
const iterator &hint) const;
/**
@@ -89,7 +90,7 @@ class KeyframeContainer {
* no chance for you to have a hint (or the container is known to be nearly
* empty)
*/
- iterator last(const time_t &time) const {
+ iterator last(const time::time_t &time) const {
return this->last(time, std::end(this->container));
}
@@ -97,14 +98,14 @@ class KeyframeContainer {
* Get the last element in the curve which is before the given time.
* (i.e. elem->time < time). Given a hint where to start the search.
*/
- iterator last_before(const time_t &time,
+ iterator last_before(const time::time_t &time,
const iterator &hint) const;
/**
* Get the last element with elem->time < time, without a hint where to start
* searching.
*/
- iterator last_before(const time_t &time) const {
+ iterator last_before(const time::time_t &time) const {
return this->last_before(time, std::end(this->container));
}
@@ -134,7 +135,7 @@ class KeyframeContainer {
* discouraged, use it only, if your really do not have the possibility to
* get a hint.
*/
- iterator insert_before(const time_t &time, const T &value) {
+ iterator insert_before(const time::time_t &time, const T &value) {
return this->insert_before(keyframe_t(time, value), std::end(this->container));
}
@@ -143,7 +144,7 @@ class KeyframeContainer {
* If there is a value with identical time, this will insert the new value
* before the old one.
*/
- iterator insert_before(const time_t &time, const T &value, const iterator &hint) {
+ iterator insert_before(const time::time_t &time, const T &value, const iterator &hint) {
return this->insert_before(keyframe_t(time, value), hint);
}
@@ -164,7 +165,7 @@ class KeyframeContainer {
* from the end of the data. The use of this function is discouraged, use it
* only, if your really do not have the possibility to get a hint.
*/
- iterator insert_overwrite(const time_t &time, const T &value) {
+ iterator insert_overwrite(const time::time_t &time, const T &value) {
return this->insert_overwrite(keyframe_t(time, value),
std::end(this->container));
}
@@ -176,7 +177,7 @@ class KeyframeContainer {
* Provide a insertion hint to abbreviate the search for the
* insertion point.
*/
- iterator insert_overwrite(const time_t &time,
+ iterator insert_overwrite(const time::time_t &time,
const T &value,
const iterator &hint,
bool overwrite_all = false) {
@@ -196,7 +197,7 @@ class KeyframeContainer {
* time from the end of the data. The use of this function is discouraged,
* use it only, if your really do not have the possibility to get a hint.
*/
- iterator insert_after(const time_t &time, const T &value) {
+ iterator insert_after(const time::time_t &time, const T &value) {
return this->insert_after(keyframe_t(time, value),
std::end(this->container));
}
@@ -205,7 +206,7 @@ class KeyframeContainer {
* Create and insert a new element, which is added after a previous element with
* identical time. Provide a insertion hint to abbreviate the search for the insertion point.
*/
- iterator insert_after(const time_t &time, const T &value, const iterator &hint) {
+ iterator insert_after(const time::time_t &time, const T &value, const iterator &hint) {
return this->insert_after(keyframe_t(time, value), hint);
}
@@ -225,7 +226,7 @@ class KeyframeContainer {
* Variant without hint, starts the search at the end of the container.
* Returns the iterator after the deleted elements.
*/
- iterator erase(const time_t &time) {
+ iterator erase(const time::time_t &time) {
return this->erase(time, std::end(this->container));
}
@@ -238,7 +239,7 @@ class KeyframeContainer {
* Or, if no elements with this time exist,
* the iterator to the first element after the requested time is returned
*/
- iterator erase(const time_t &time,
+ iterator erase(const time::time_t &time,
const iterator &hint) {
return this->erase_group(time, this->last(time, hint));
}
@@ -279,8 +280,8 @@ class KeyframeContainer {
* Using the default value replaces ALL keyframes of \p this with
* the keyframes of \p other.
*/
- iterator sync_after(const KeyframeContainer &other,
- const time_t &start = std::numeric_limits::min());
+ iterator sync(const KeyframeContainer &other,
+ const time::time_t &start = std::numeric_limits::min());
/**
* Copy keyframes from another container (with a different element type) to this container.
@@ -295,9 +296,9 @@ class KeyframeContainer {
* the keyframes of \p other.
*/
template
- iterator sync_after(const KeyframeContainer &other,
- const std::function &converter,
- const time_t &start = std::numeric_limits::min());
+ iterator sync(const KeyframeContainer &other,
+ const std::function &converter,
+ const time::time_t &start = std::numeric_limits::min());
/**
* Debugging method to be used from gdb to understand bugs better.
@@ -313,7 +314,7 @@ class KeyframeContainer {
* Erase elements with this time.
* The iterator has to point to the last element of the same-time group.
*/
- iterator erase_group(const time_t &time,
+ iterator erase_group(const time::time_t &time,
const iterator &last_elem);
/**
@@ -327,7 +328,7 @@ template
KeyframeContainer::KeyframeContainer() {
// Create a default element at -Inf, that can always be dereferenced - so
// there will by definition never be a element that cannot be dereferenced
- this->container.push_back(keyframe_t(std::numeric_limits::min(), T()));
+ this->container.push_back(keyframe_t(std::numeric_limits::min(), T()));
}
@@ -335,7 +336,7 @@ template
KeyframeContainer::KeyframeContainer(const T &defaultval) {
// Create a default element at -Inf, that can always be dereferenced - so
// there will by definition never be a element that cannot be dereferenced
- this->container.push_back(keyframe_t(std::numeric_limits::min(), defaultval));
+ this->container.push_back(keyframe_t(std::numeric_limits::min(), defaultval));
}
@@ -354,7 +355,7 @@ size_t KeyframeContainer::size() const {
* that determines the curve value for a searched time.
*/
template
-typename KeyframeContainer::iterator KeyframeContainer::last(const time_t &time,
+typename KeyframeContainer::iterator KeyframeContainer::last(const time::time_t &time,
const iterator &hint) const {
iterator e = hint;
auto end = std::end(this->container);
@@ -388,7 +389,7 @@ typename KeyframeContainer::iterator KeyframeContainer::last(const time_t
* first element that matches the search time.
*/
template
-typename KeyframeContainer::iterator KeyframeContainer::last_before(const time_t &time,
+typename KeyframeContainer::iterator KeyframeContainer::last_before(const time::time_t &time,
const iterator &hint) const {
iterator e = hint;
auto end = std::end(this->container);
@@ -511,8 +512,8 @@ KeyframeContainer::erase(KeyframeContainer::iterator e) {
template
typename KeyframeContainer::iterator
-KeyframeContainer::sync_after(const KeyframeContainer &other,
- const time_t &start) {
+KeyframeContainer::sync(const KeyframeContainer &other,
+ const time::time_t &start) {
// Delete elements after start time
iterator at = this->last_before(start, this->end());
at = this->erase_after(at);
@@ -535,9 +536,9 @@ KeyframeContainer::sync_after(const KeyframeContainer &other,
template
template
typename KeyframeContainer::iterator
-KeyframeContainer::sync_after(const KeyframeContainer &other,
- const std::function &converter,
- const time_t &start) {
+KeyframeContainer::sync(const KeyframeContainer &other,
+ const std::function &converter,
+ const time::time_t &start) {
// Delete elements after start time
iterator at = this->last_before(start, this->end());
at = this->erase_after(at);
@@ -560,7 +561,7 @@ KeyframeContainer::sync_after(const KeyframeContainer &other,
template
typename KeyframeContainer::iterator
-KeyframeContainer::erase_group(const time_t &time,
+KeyframeContainer::erase_group(const time::time_t &time,
const iterator &last_elem) {
iterator at = last_elem;
diff --git a/libopenage/curve/map.h b/libopenage/curve/map.h
index 8fefda4023..3a8374a128 100644
--- a/libopenage/curve/map.h
+++ b/libopenage/curve/map.h
@@ -1,13 +1,15 @@
-// Copyright 2017-2018 the openage authors. See copying.md for legal info.
+// Copyright 2017-2023 the openage authors. See copying.md for legal info.
#pragma once
-#include "curve.h"
-#include "map_filter_iterator.h"
-
-#include
#include
+#include
#include
+#include
+
+#include "curve/map_filter_iterator.h"
+#include "time/time.h"
+#include "util/fixed_point.h"
namespace openage::curve {
@@ -20,15 +22,14 @@ template
class UnorderedMap {
/** Internal container to access all data and metadata */
struct map_element {
- map_element (const val_t &v, const time_t &a, const time_t &d)
- :
+ map_element(const val_t &v, const time::time_t &a, const time::time_t &d) :
value(v),
alive(a),
dead(d) {}
val_t value;
- time_t alive;
- time_t dead;
+ time::time_t alive;
+ time::time_t dead;
};
/**
@@ -41,97 +42,94 @@ class UnorderedMap {
using const_iterator = typename std::unordered_map::const_iterator;
std::optional>
- operator ()(const time_t&, const key_t &) const;
+ operator()(const time::time_t &, const key_t &) const;
std::optional>
- at(const time_t &, const key_t &) const;
+ at(const time::time_t &, const key_t &) const;
MapFilterIterator
- begin(const time_t &e = std::numeric_limits::max()) const;
+ begin(const time::time_t &e = std::numeric_limits