Skip to content

ApplyEffect implementation #1688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 52 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
24fda9b
gamestate: Apply*Effect components.
heinezen Sep 1, 2024
4b5a3eb
gamestate: Create Apply*Effect components for new entities.
heinezen Sep 1, 2024
87877d1
gamestate: API interface for Apply*Effect abilities.
heinezen Sep 1, 2024
0701874
gamestate: Fix checking ability parents.
heinezen Sep 1, 2024
8768cf5
gamestate: Add Resistance component.
heinezen Sep 7, 2024
d023e68
gamestate: Add basic system skeleton for applying effects.
heinezen Sep 7, 2024
c8cf31f
gamestate: resistance definitions.
heinezen Sep 7, 2024
70bf6c3
gamestate: API layer for nyan effects.
heinezen Sep 7, 2024
b21f4bc
gamestate: API layer for nyan resistances.
heinezen Sep 7, 2024
f3602c2
gamestate: Add LineOfSight component.
heinezen Sep 7, 2024
ad2771d
gamestate: LineOfSight definitions.
heinezen Sep 7, 2024
e2f8db3
gamestate: Add missing definitions for already implemented abilities.
heinezen Sep 7, 2024
5734740
gamestate: Allow fractional values for attributes.
heinezen Sep 8, 2024
9997df4
gamestate: Calculate application for discrete FLAC effects.
heinezen Sep 8, 2024
7aebc01
gamestate: Decrease log level of unrecognized components.
heinezen Sep 13, 2024
29e9e49
gamestate: ApplyEffect command.
heinezen Sep 14, 2024
203e0df
gamestate: Rename command classes and make them 'final'.
heinezen Sep 14, 2024
be72989
gamestate: Add condition for ApplyEffect command in activity system.
heinezen Sep 15, 2024
871701c
convert: Add new activity conditions for applying effects.
heinezen Sep 15, 2024
e38affa
gamestate: Handle ApplyEffect in activity system.
heinezen Sep 15, 2024
d1b6b54
gamestate: Fix time calculations for applying effects.
heinezen Sep 15, 2024
c97a028
gamestate: Move animation property handling to helper function.
heinezen Sep 15, 2024
7d12074
gamestate: Animate effect application.
heinezen Sep 15, 2024
69e97dc
curve: Add compress argument for curve operations.
heinezen Oct 16, 2024
a0df267
curve: Rename argument for keyframes to 'keyframe'.
heinezen Oct 16, 2024
646aa0b
curve: Compress operation on keyframe insertion.
heinezen Oct 16, 2024
4804982
curve: Compress method for curves.
heinezen Oct 18, 2024
d99559e
curve: Add new unit tests for compress() method.
heinezen Oct 18, 2024
781e9df
curve: Fix compression method.
heinezen Oct 18, 2024
f6a4575
curve: Pass through compression args.
heinezen Oct 19, 2024
2e3a1f0
curve: Compress during curve sync.
heinezen Oct 19, 2024
7d299d9
renderer: Compress sync on animations curve.
heinezen Oct 19, 2024
1654953
renderer: Make fetching from render entity more reliable.
heinezen Oct 20, 2024
ea12020
curve: Fix compilation for oider clang versions.
heinezen Oct 20, 2024
eda3ed9
curve: Concept for curve values.
heinezen Oct 20, 2024
4ef068d
doc: Add documentation for curve compression.
heinezen Oct 22, 2024
438132f
gamestate: Add activity node type for branching on value.
heinezen Nov 4, 2024
4334328
gamestate: Handle XorSwichGate in activity system.
heinezen Nov 5, 2024
f841248
gamestate: Add unit tests for activity node types.
heinezen Nov 5, 2024
bd270d1
gamestate: Make a lookup function for next comand switching.
heinezen Nov 5, 2024
0f989d0
convert: Use new switch gate for command branching.
heinezen Nov 5, 2024
a4b8864
gamestate: Init new XorSwitchGate activity node type from nyan.
heinezen Nov 7, 2024
e7ee1cb
gamestate: Resolve lookup func and node ID mapping for switch condition.
heinezen Apr 7, 2025
6f7b5d3
gamestate: Correctly subtract/add applied attribute value.
heinezen Apr 7, 2025
c01ce76
gamestate: Fix missing convert effect type.
heinezen Apr 9, 2025
5fa1f64
convert: Fix generated path of shared media files in modpack.
heinezen Apr 9, 2025
1fb7846
gamestate: Only add apply effect command to game entities with matchi…
heinezen Apr 10, 2025
2d4f163
curve: Fix wrong assertion for interpolation.
heinezen Apr 10, 2025
e2d2180
util: Get absolute difference between two fixed point values.
heinezen Apr 10, 2025
da877aa
curve: Fix overflow for interpolation time offsets.
heinezen Apr 10, 2025
a6bba99
util: Add concepts for fixed point types.
heinezen Apr 11, 2025
788cc1f
curve: Cleanup docstrings in KeyframeContainer class.
heinezen Apr 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions doc/code/curves.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Curves are an integral part of openage's event-based game simulation.
1. [Queue](#queue)
2. [Unordered Map](#unordered-map)
3. [Array](#array)
4. [Compression](#compression)


## Motivation
Expand Down Expand Up @@ -133,6 +134,9 @@ Modify operations insert values for a specific point in time.
| `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` |
| `compress(t)` | Remove redundant keyframes at and after time `t`; see [Compression] for more info |

[Compression]: #compression

**Copy**

Expand Down Expand Up @@ -292,3 +296,28 @@ Modify operations insert values for a specific point in time.
| Method | Description |
| ---------------- | ------------------------------------------------------------------------------------------------ |
| `sync(Curve, t)` | Replace all keyframes from self after time `t` with keyframes from source `Curve` after time `t` |


## Compression

Curves support basic lossless compression by removing redundant keyframes from the curve.
Keyframes are considered redundant if they do not change any interpolation results, i.e.
the result of `get(t)` does not change.

The most straight-forward way to use compression with primitive curves is the `compress(t)`
method. `compress(t)` iterates over the curve and removes all redundant keyframes after
or at time `t`. The runtime has linear complexity `O(n)` based on the number of elements
in the keyframe container.

Furthermore, primitive curves support incremental compression during insertion for the
`set_insert(t, value)` and `set_last(t, value)` methods via their `compress` argument.
If compression is active, `(t, value)` is only inserted when it is not a redundant
keyframe. `sync(Curve, t)` also supports compression with a flag `compress` passed as
an argument.

Compression may be used in cases where the size should be kept small, e.g. when the curve
is tranferred via network or recorded in a replay file. Another application of compression
is in the [renderer](/doc/code/renderer/README.md) for the discrete curves storing an object's
animations. Since compression removes redundant animation entries, the renderer can determine
when the current animation has started much easier as this is then returned by the keyframe
time in `frame(t)`.
17 changes: 9 additions & 8 deletions doc/code/game_simulation/activity.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ you can use available [BPMN tools](https://bpmn.io/) to draw activity node graph
## 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 |
| 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 |
| `XOR_SWITCH_GATE` | 1 | 1+ | Branch on value |
1 change: 1 addition & 0 deletions libopenage/curve/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_sources(libopenage
base_curve.cpp
concept.cpp
continuous.cpp
discrete.cpp
discrete_mod.cpp
Expand Down
119 changes: 95 additions & 24 deletions libopenage/curve/base_curve.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright 2017-2025 the openage authors. See copying.md for legal info.
// Copyright 2017-2024 the openage authors. See copying.md for legal info.

#pragma once

#include <concepts>
#include <cstddef>
#include <functional>
#include <memory>
Expand All @@ -13,6 +14,7 @@
#include "log/log.h"
#include "log/message.h"

#include "curve/concept.h"
#include "curve/keyframe_container.h"
#include "event/evententity.h"
#include "time/time.h"
Expand All @@ -26,7 +28,7 @@ class EventLoop;

namespace curve {

template <typename T>
template <KeyframeValueLike T>
class BaseCurve : public event::EventEntity {
public:
BaseCurve(const std::shared_ptr<event::EventLoop> &loop,
Expand Down Expand Up @@ -74,30 +76,62 @@ class BaseCurve : public event::EventEntity {
/**
* Insert/overwrite given value at given time and erase all elements
* that follow at a later time.
*
* If multiple elements exist at the given time,
* overwrite the last one.
*
* @param at Time the keyframe is inserted at.
* @param value Value of the keyframe.
* @param compress If true, only insert the keyframe if the value at time \p at
* is different from the given value.
*/
virtual void set_last(const time::time_t &at, const T &value);
virtual void set_last(const time::time_t &at,
const T &value,
bool compress = false);

/**
* 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.
*
* @param at Time the keyframe is inserted at.
* @param value Value of the keyframe.
* @param compress If true, only insert the keyframe if the value at time \p at
* is different from the given value.
*/
virtual void set_insert(const time::time_t &at, const T &value);
virtual void set_insert(const time::time_t &at,
const T &value,
bool compress = false);

/**
* 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.
*
* @param at Time the keyframe is inserted at.
* @param value Value of the keyframe.
*/
virtual void set_replace(const time::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::time_t &at);

/**
* Compress the curve by removing redundant keyframes.
*
* A keyframe is redundant if it doesn't change the value calculation of the curve
* at any given time, e.g. duplicate keyframes.
*
* @param start Start time at which keyframes are compressed (default = -INF).
* Using the default value compresses ALL keyframes of the curve.
*/
virtual void compress(const time::time_t &start = time::TIME_MIN) = 0;

/**
* Integrity check, for debugging/testing reasons only.
*/
Expand All @@ -113,9 +147,13 @@ class BaseCurve : public event::EventEntity {
* @param start Start time at which keyframes are replaced (default = -INF).
* Using the default value replaces ALL keyframes of \p this with
* the keyframes of \p other.
* @param compress If true, redundant keyframes are not copied during the sync.
* Redundant keyframes are keyframes that don't change the value
* calculaton of the curve at any given time, e.g. duplicate keyframes.
*/
void sync(const BaseCurve<T> &other,
const time::time_t &start = time::TIME_MIN);
const time::time_t &start = time::TIME_MIN,
bool compress = false);

/**
* Copy keyframes from another curve (with a different element type) to this curve.
Expand All @@ -130,11 +168,15 @@ class BaseCurve : public event::EventEntity {
* @param start Start time at which keyframes are replaced (default = -INF).
* Using the default value replaces ALL keyframes of \p this with
* the keyframes of \p other.
* @param compress If true, redundant keyframes are not copied during the sync.
* Redundant keyframes are keyframes that don't change the value
* calculaton of the curve at any given time, e.g. duplicate keyframes.
*/
template <typename O>
template <KeyframeValueLike O>
void sync(const BaseCurve<O> &other,
const std::function<T(const O &)> &converter,
const time::time_t &start = time::TIME_MIN);
const time::time_t &start = time::TIME_MIN,
bool compress = false);

/**
* Get the identifier of this curve.
Expand Down Expand Up @@ -199,8 +241,10 @@ class BaseCurve : public event::EventEntity {
};


template <typename T>
void BaseCurve<T>::set_last(const time::time_t &at, const T &value) {
template <KeyframeValueLike T>
void BaseCurve<T>::set_last(const time::time_t &at,
const T &value,
bool compress) {
auto hint = this->container.last(at, this->last_element);

// erase max one same-time value
Expand All @@ -210,55 +254,72 @@ void BaseCurve<T>::set_last(const time::time_t &at, const T &value) {

hint = this->container.erase_after(hint);

if (compress and this->get(at) == value) {
// skip insertion if the value is the same as the last one
// erasure still happened, so we need to notify about the change
this->changes(at);
return;
}

this->container.insert_before(at, value, hint);
this->last_element = hint;

this->changes(at);
}


template <typename T>
void BaseCurve<T>::set_insert(const time::time_t &at, const T &value) {
template <KeyframeValueLike T>
void BaseCurve<T>::set_insert(const time::time_t &at,
const T &value,
bool compress) {
if (compress and this->get(at) == value) {
// skip insertion if the value is the same as the last one
return;
}

auto hint = this->container.insert_after(at, value, this->last_element);

// check if this is now the final keyframe
if (this->container.get(hint).time() > this->container.get(this->last_element).time()) {
this->last_element = hint;
}

this->changes(at);
}


template <typename T>
void BaseCurve<T>::set_replace(const time::time_t &at, const T &value) {
template <KeyframeValueLike T>
void BaseCurve<T>::set_replace(const time::time_t &at,
const T &value) {
this->container.insert_overwrite(at, value, this->last_element);
this->changes(at);
}


template <typename T>
template <KeyframeValueLike T>
void BaseCurve<T>::erase(const time::time_t &at) {
this->last_element = this->container.erase(at, this->last_element);
this->changes(at);
}


template <typename T>
template <KeyframeValueLike T>
std::pair<time::time_t, const T> BaseCurve<T>::frame(const time::time_t &time) const {
auto e = this->container.last(time, this->container.size());
auto elem = this->container.get(e);
return elem.as_pair();
}


template <typename T>
template <KeyframeValueLike T>
std::pair<time::time_t, const T> BaseCurve<T>::next_frame(const time::time_t &time) const {
auto e = this->container.last(time, this->container.size());
e++;
auto elem = this->container.get(e);
return elem.as_pair();
}

template <typename T>
template <KeyframeValueLike T>
std::string BaseCurve<T>::str() const {
std::stringstream ss;
ss << "Curve[" << this->idstr() << "]{" << std::endl;
Expand All @@ -270,7 +331,7 @@ std::string BaseCurve<T>::str() const {
return ss.str();
}

template <typename T>
template <KeyframeValueLike T>
void BaseCurve<T>::check_integrity() const {
time::time_t last_time = time::TIME_MIN;
for (const auto &keyframe : this->container) {
Expand All @@ -281,9 +342,10 @@ void BaseCurve<T>::check_integrity() const {
}
}

template <typename T>
template <KeyframeValueLike T>
void BaseCurve<T>::sync(const BaseCurve<T> &other,
const time::time_t &start) {
const time::time_t &start,
bool compress) {
// Copy keyframes between containers for t >= start
this->last_element = this->container.sync(other.container, start);

Expand All @@ -294,15 +356,20 @@ void BaseCurve<T>::sync(const BaseCurve<T> &other,
this->set_insert(start, get_other);
}

if (compress) {
this->compress(start);
}

this->changes(start);
}


template <typename T>
template <typename O>
template <KeyframeValueLike T>
template <KeyframeValueLike O>
void BaseCurve<T>::sync(const BaseCurve<O> &other,
const std::function<T(const O &)> &converter,
const time::time_t &start) {
const time::time_t &start,
bool compress) {
// Copy keyframes between containers for t >= start
this->last_element = this->container.sync(other.get_container(), converter, start);

Expand All @@ -313,6 +380,10 @@ void BaseCurve<T>::sync(const BaseCurve<O> &other,
this->set_insert(start, get_other);
}

if (compress) {
this->compress(start);
}

this->changes(start);
}

Expand Down
9 changes: 9 additions & 0 deletions libopenage/curve/concept.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2024-2024 the openage authors. See copying.md for legal info.

#include "concept.h"


namespace openage::curve {


} // namespace openage::curve
15 changes: 15 additions & 0 deletions libopenage/curve/concept.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2024-2024 the openage authors. See copying.md for legal info.

#pragma once

#include <concepts>

namespace openage::curve {

/**
* Concept for keyframe values.
*/
template <typename T>
concept KeyframeValueLike = std::copyable<T> && std::equality_comparable<T>;

} // namespace openage::curve
5 changes: 3 additions & 2 deletions libopenage/curve/container/iterator.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright 2017-2025 the openage authors. See copying.md for legal info.
// Copyright 2017-2024 the openage authors. See copying.md for legal info.

#pragma once

#include "curve/concept.h"
#include "time/time.h"
#include "util/fixed_point.h"

Expand All @@ -11,7 +12,7 @@ namespace openage::curve {
/**
* Default interface for curve containers
*/
template <typename val_t,
template <KeyframeValueLike val_t,
typename container_t,
typename iterator_t = typename container_t::const_iterator>
class CurveIterator {
Expand Down
Loading
Loading