From f9fd7ceff606e71f9c31f9611ea9d97ea4aa5d45 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:16:59 +0200 Subject: [PATCH 01/42] gamestate: Apply*Effect components. --- .../gamestate/component/api/CMakeLists.txt | 1 + .../gamestate/component/api/apply_effect.cpp | 45 ++++++++++ .../gamestate/component/api/apply_effect.h | 85 +++++++++++++++++++ libopenage/gamestate/component/types.h | 3 +- 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 libopenage/gamestate/component/api/apply_effect.cpp create mode 100644 libopenage/gamestate/component/api/apply_effect.h diff --git a/libopenage/gamestate/component/api/CMakeLists.txt b/libopenage/gamestate/component/api/CMakeLists.txt index 8588909bf2..6ca89437f9 100644 --- a/libopenage/gamestate/component/api/CMakeLists.txt +++ b/libopenage/gamestate/component/api/CMakeLists.txt @@ -1,4 +1,5 @@ add_sources(libopenage + apply_effect.cpp idle.cpp live.cpp move.cpp diff --git a/libopenage/gamestate/component/api/apply_effect.cpp b/libopenage/gamestate/component/api/apply_effect.cpp new file mode 100644 index 0000000000..5134b909a8 --- /dev/null +++ b/libopenage/gamestate/component/api/apply_effect.cpp @@ -0,0 +1,45 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "apply_effect.h" + + +namespace openage::gamestate::component { + +ApplyEffect::ApplyEffect(const std::shared_ptr &loop, + nyan::Object &ability, + const time::time_t &creation_time, + bool enabled) : + APIComponent{loop, ability, creation_time, enabled}, + init_time{loop, 0}, + last_used{loop, 0} { +} + +ApplyEffect::ApplyEffect(const std::shared_ptr &loop, + nyan::Object &ability, + bool enabled) : + APIComponent{loop, ability, enabled}, + init_time{loop, 0}, + last_used{loop, 0} { +} + +component_t ApplyEffect::get_type() const { + return component_t::APPLY_EFFECT; +} + +const curve::Discrete &ApplyEffect::get_init_time() const { + return this->init_time; +} + +const curve::Discrete &ApplyEffect::get_last_used() const { + return this->last_used; +} + +void ApplyEffect::set_init_time(const time::time_t &time) { + this->init_time.set_last(time, time); +} + +void ApplyEffect::set_last_used(const time::time_t &time) { + this->last_used.set_last(time, time); +} + +} // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/api/apply_effect.h b/libopenage/gamestate/component/api/apply_effect.h new file mode 100644 index 0000000000..0411a5abc2 --- /dev/null +++ b/libopenage/gamestate/component/api/apply_effect.h @@ -0,0 +1,85 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "curve/discrete.h" +#include "gamestate/component/api_component.h" +#include "gamestate/component/types.h" +#include "time/time.h" + + +namespace openage::gamestate::component { + +/** + * Component for ApplyEffect abilities. + */ +class ApplyEffect final : public APIComponent { +public: + ApplyEffect(const std::shared_ptr &loop, + nyan::Object &ability, + const time::time_t &creation_time, + bool enabled = true); + + ApplyEffect(const std::shared_ptr &loop, + nyan::Object &ability, + bool enabled = true); + + component_t get_type() const override; + + /** + * Get the last initiaton time that is before the given \p time. + * + * @param time Current simulation time. + * + * @return Curve with the last initiation times. + */ + const curve::Discrete &get_init_time() const; + + /** + * Get the last time the effects were applied before the given \p time. + * + * @param time Current simulation time. + * + * @return Curve with the last application times. + */ + const curve::Discrete &get_last_used() const; + + /** + * Record the simulation time when the entity starts using the ability. + * + * @param time Time at which the entity initiates using the ability. + */ + void set_init_time(const time::time_t &time); + + /** + * Record the simulation time when the entity last applied the effects. + * + * @param time Time at which the entity last applied the effects. + */ + void set_last_used(const time::time_t &time); + +private: + /** + * Simulation time when the entity starts using the corresponding ability + * of the component. For example, when a unit starts attacking. + * + * Effects are applied after \p init_time + \p application_delay (from the nyan object). + * + * The curve stores the time both as the keyframe time AND the keyframe value. In + * practice, only the value should be used. + */ + curve::Discrete init_time; + + /** + * Simulation time when the effects were applied last. + * + * Effects can only be applied again after a cooldown has passed, i.e. + * at \p last_used + \p reload_time (from the nyan object). + * + * The curve stores the time both as the keyframe time AND the keyframe value. In + * practice, only the value should be used. + */ + curve::Discrete last_used; +}; + +} // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/types.h b/libopenage/gamestate/component/types.h index 5e87b8ed23..41475dfd7a 100644 --- a/libopenage/gamestate/component/types.h +++ b/libopenage/gamestate/component/types.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -16,6 +16,7 @@ enum class component_t { ACTIVITY, // API + APPLY_EFFECT, IDLE, TURN, MOVE, From 88a8d5f0c6ac4f94109dc2ce311662eba6b1ba6c Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:17:34 +0200 Subject: [PATCH 02/42] gamestate: Create Apply*Effect components for new entities. --- libopenage/gamestate/entity_factory.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index f2f489206c..fa00dc8fa7 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "entity_factory.h" @@ -23,6 +23,7 @@ #include "gamestate/activity/xor_event_gate.h" #include "gamestate/activity/xor_gate.h" #include "gamestate/api/activity.h" +#include "gamestate/component/api/apply_effect.h" #include "gamestate/component/api/idle.h" #include "gamestate/component/api/live.h" #include "gamestate/component/api/move.h" @@ -121,7 +122,7 @@ std::shared_ptr EntityFactory::add_game_entity(const std::shared_ptr // use the owner's data to initialize the entity // this ensures that only the owner's tech upgrades apply auto db_view = state->get_player(owner_id)->get_db_view(); - init_components(loop, db_view, entity, nyan_entity); + this->init_components(loop, db_view, entity, nyan_entity); if (this->render_factory) { entity->set_render_entity(this->render_factory->add_world_render_entity()); @@ -208,6 +209,14 @@ void EntityFactory::init_components(const std::shared_ptr(loop, ability_obj); entity->add_component(selectable); } + else if (ability_parent == "engine.ability.type.ApplyContinuousEffect" + or ability_parent == "engine.ability.type.ApplyDiscreteEffect") { + auto apply_effect = std::make_shared(loop, ability_obj); + entity->add_component(apply_effect); + } + else { + log::log(DBG << "Entity has unrecognized ability type: " << ability_parent); + } } if (activity_ability) { From 99a84318a5fc36fb6d650dd80b73c63b24c74a44 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:46:56 +0200 Subject: [PATCH 03/42] gamestate: API interface for Apply*Effect abilities. --- libopenage/gamestate/api/ability.cpp | 19 +++++++++++++------ libopenage/gamestate/api/ability.h | 11 +++++++++++ libopenage/gamestate/api/definitions.h | 10 +++++++++- libopenage/gamestate/api/types.h | 6 +++++- libopenage/gamestate/entity_factory.cpp | 4 +++- 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/libopenage/gamestate/api/ability.cpp b/libopenage/gamestate/api/ability.cpp index 49ecfad48f..35c24ac65b 100644 --- a/libopenage/gamestate/api/ability.cpp +++ b/libopenage/gamestate/api/ability.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "ability.h" @@ -20,15 +20,22 @@ bool APIAbility::is_ability(const nyan::Object &obj) { return immediate_parent == "engine.ability.Ability"; } +bool APIAbility::check_type(const nyan::Object &ability, + const ability_t &type) { + nyan::fqon_t immediate_parent = ability.get_parents()[0]; + nyan::ValueHolder ability_type = ABILITY_DEFS.get(type); + + std::shared_ptr ability_val = std::dynamic_pointer_cast( + ability_type.get_ptr()); + + return ability_val->get_name() == immediate_parent; +} + bool APIAbility::check_property(const nyan::Object &ability, const ability_property_t &property) { std::shared_ptr properties = ability.get("Ability.properties"); nyan::ValueHolder property_type = ABILITY_PROPERTY_DEFS.get(property); - if (properties->contains(property_type)) { - return true; - } - - return false; + return properties->contains(property_type); } diff --git a/libopenage/gamestate/api/ability.h b/libopenage/gamestate/api/ability.h index d0ba6ea54e..1df65a95fd 100644 --- a/libopenage/gamestate/api/ability.h +++ b/libopenage/gamestate/api/ability.h @@ -22,6 +22,17 @@ class APIAbility { */ static bool is_ability(const nyan::Object &obj); + /** + * Check if an ability is of a given type. + * + * @param ability \p Ability nyan object (type == \p engine.ability.Ability). + * @param type Ability type. + * + * @return true if the ability is of the given type, else false. + */ + static bool check_type(const nyan::Object &ability, + const ability_t &type); + /** * Check if an ability has a given property. * diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index f982571f94..df0d8562ac 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once @@ -25,6 +25,14 @@ namespace openage::gamestate::api { * Maps internal ability types to nyan API values. */ static const auto ABILITY_DEFS = datastructure::create_const_map( + std::pair(ability_t::APPLY_CONTINUOUS_EFFECT, + nyan::ValueHolder(std::make_shared("engine.ability.type.ApplyContinuousEffect"))), + std::pair(ability_t::APPLY_DISCRETE_EFFECT, + nyan::ValueHolder(std::make_shared("engine.ability.type.ApplyDiscreteEffect"))), + std::pair(ability_t::RANGED_CONTINUOUS_EFFECT, + nyan::ValueHolder(std::make_shared("engine.ability.type.RangedContinuousEffect"))), + std::pair(ability_t::RANGED_DISCRETE_EFFECT, + nyan::ValueHolder(std::make_shared("engine.ability.type.RangedDiscreteEffect"))), std::pair(ability_t::IDLE, nyan::ValueHolder(std::make_shared("engine.ability.type.Idle"))), std::pair(ability_t::MOVE, diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index 2c44fe1e81..a1fed720e0 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once @@ -9,6 +9,10 @@ namespace openage::gamestate::api { * Types of abilities for API objects. */ enum class ability_t { + APPLY_CONTINUOUS_EFFECT, + APPLY_DISCRETE_EFFECT, + RANGED_CONTINUOUS_EFFECT, + RANGED_DISCRETE_EFFECT, IDLE, LIVE, MOVE, diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index fa00dc8fa7..12f1bab392 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -210,7 +210,9 @@ void EntityFactory::init_components(const std::shared_ptradd_component(selectable); } else if (ability_parent == "engine.ability.type.ApplyContinuousEffect" - or ability_parent == "engine.ability.type.ApplyDiscreteEffect") { + or ability_parent == "engine.ability.type.ApplyDiscreteEffect" + or ability_parent == "engine.ability.type.RangedContinuousEffect" + or ability_parent == "engine.ability.type.RangedDiscreteEffect") { auto apply_effect = std::make_shared(loop, ability_obj); entity->add_component(apply_effect); } From 45e952323f93d1ad8795794774450b7811e130f1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:49:41 +0200 Subject: [PATCH 04/42] gamestate: Fix checking ability parents. --- libopenage/gamestate/api/ability.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/api/ability.cpp b/libopenage/gamestate/api/ability.cpp index 35c24ac65b..1fc24c064b 100644 --- a/libopenage/gamestate/api/ability.cpp +++ b/libopenage/gamestate/api/ability.cpp @@ -16,8 +16,13 @@ namespace openage::gamestate::api { bool APIAbility::is_ability(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.ability.Ability"; + for (auto &parent : obj.get_parents()) { + if (parent == "engine.ability.Ability") { + return true; + } + } + + return false; } bool APIAbility::check_type(const nyan::Object &ability, From 5f6822ed906a12a194af02030e3e4bf5470a4c8d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:04:54 +0200 Subject: [PATCH 05/42] gamestate: Add Resistance component. --- .../gamestate/component/api/CMakeLists.txt | 1 + .../gamestate/component/api/resistance.cpp | 12 ++++++++++++ .../gamestate/component/api/resistance.h | 18 ++++++++++++++++++ libopenage/gamestate/component/types.h | 3 ++- libopenage/gamestate/entity_factory.cpp | 5 +++++ 5 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 libopenage/gamestate/component/api/resistance.cpp create mode 100644 libopenage/gamestate/component/api/resistance.h diff --git a/libopenage/gamestate/component/api/CMakeLists.txt b/libopenage/gamestate/component/api/CMakeLists.txt index 6ca89437f9..138c17fc0f 100644 --- a/libopenage/gamestate/component/api/CMakeLists.txt +++ b/libopenage/gamestate/component/api/CMakeLists.txt @@ -3,6 +3,7 @@ add_sources(libopenage idle.cpp live.cpp move.cpp + resistance.cpp selectable.cpp turn.cpp ) diff --git a/libopenage/gamestate/component/api/resistance.cpp b/libopenage/gamestate/component/api/resistance.cpp new file mode 100644 index 0000000000..9a63b601b3 --- /dev/null +++ b/libopenage/gamestate/component/api/resistance.cpp @@ -0,0 +1,12 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "resistance.h" + + +namespace openage::gamestate::component { + +component_t Resistance::get_type() const { + return component_t::RESISTANCE; +} + +} // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/api/resistance.h b/libopenage/gamestate/component/api/resistance.h new file mode 100644 index 0000000000..78dba25f87 --- /dev/null +++ b/libopenage/gamestate/component/api/resistance.h @@ -0,0 +1,18 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "gamestate/component/api_component.h" +#include "gamestate/component/types.h" + + +namespace openage::gamestate::component { + +class Resistance final : public APIComponent { +public: + using APIComponent::APIComponent; + + component_t get_type() const override; +}; + +} // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/types.h b/libopenage/gamestate/component/types.h index 41475dfd7a..4c2d62cdc6 100644 --- a/libopenage/gamestate/component/types.h +++ b/libopenage/gamestate/component/types.h @@ -17,11 +17,12 @@ enum class component_t { // API APPLY_EFFECT, + RESISTANCE, IDLE, TURN, MOVE, SELECTABLE, - LIVE + LIVE, }; } // namespace openage::gamestate::component diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 12f1bab392..836ec281a1 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -27,6 +27,7 @@ #include "gamestate/component/api/idle.h" #include "gamestate/component/api/live.h" #include "gamestate/component/api/move.h" +#include "gamestate/component/api/resistance.h" #include "gamestate/component/api/selectable.h" #include "gamestate/component/api/turn.h" #include "gamestate/component/internal/activity.h" @@ -216,6 +217,10 @@ void EntityFactory::init_components(const std::shared_ptr(loop, ability_obj); entity->add_component(apply_effect); } + else if (ability_parent == "engine.ability.type.Resistance") { + auto resistance = std::make_shared(loop, ability_obj); + entity->add_component(resistance); + } else { log::log(DBG << "Entity has unrecognized ability type: " << ability_parent); } From 736a60eb0bcc7327bcc34995f1f5045e0efc0d74 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:06:04 +0200 Subject: [PATCH 06/42] gamestate: Add basic system skeleton for applying effects. --- libopenage/gamestate/system/CMakeLists.txt | 1 + libopenage/gamestate/system/apply_effect.cpp | 16 +++++++++ libopenage/gamestate/system/apply_effect.h | 36 ++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 libopenage/gamestate/system/apply_effect.cpp create mode 100644 libopenage/gamestate/system/apply_effect.h diff --git a/libopenage/gamestate/system/CMakeLists.txt b/libopenage/gamestate/system/CMakeLists.txt index ec2ea97945..389ddf1114 100644 --- a/libopenage/gamestate/system/CMakeLists.txt +++ b/libopenage/gamestate/system/CMakeLists.txt @@ -1,5 +1,6 @@ add_sources(libopenage activity.cpp + apply_effect.cpp idle.cpp move.cpp types.cpp diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp new file mode 100644 index 0000000000..8beb3292c1 --- /dev/null +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -0,0 +1,16 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "apply_effect.h" + + +namespace openage::gamestate::system { + +const time::time_t ApplyEffect::apply_effect(const std::shared_ptr &entity, + const std::shared_ptr &state, + const std::shared_ptr &target, + const time::time_t &start_time) { + // TODO: Implement + return start_time; +} + +} // namespace openage::gamestate::system diff --git a/libopenage/gamestate/system/apply_effect.h b/libopenage/gamestate/system/apply_effect.h new file mode 100644 index 0000000000..c00d7fbf78 --- /dev/null +++ b/libopenage/gamestate/system/apply_effect.h @@ -0,0 +1,36 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "time/time.h" + + +namespace openage { + +namespace gamestate { +class GameEntity; +class GameState; + +namespace system { + + +class ApplyEffect { + /** + * Apply the effect of an ability to a game entity. + * + * @param entity Game entity applying the effects. + * @param state Game state. + * @param target Target entity of the effects. + * @param start_time Start time of change. + * + * @return Runtime of the change in simulation time. + */ + static const time::time_t apply_effect(const std::shared_ptr &entity, + const std::shared_ptr &state, + const std::shared_ptr &target, + const time::time_t &start_time); +}; + +} // namespace system +} // namespace gamestate +} // namespace openage From 198d8482bb0c098e27f7d8739a55b1d5c2397b9e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:12:37 +0200 Subject: [PATCH 07/42] gamestate: resistance definitions. --- libopenage/gamestate/api/definitions.h | 2 ++ libopenage/gamestate/api/types.h | 1 + 2 files changed, 3 insertions(+) diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index df0d8562ac..c48e56da7d 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -33,6 +33,8 @@ static const auto ABILITY_DEFS = datastructure::create_const_map("engine.ability.type.RangedContinuousEffect"))), std::pair(ability_t::RANGED_DISCRETE_EFFECT, nyan::ValueHolder(std::make_shared("engine.ability.type.RangedDiscreteEffect"))), + std::pair(ability_t::RESISTANCE, + nyan::ValueHolder(std::make_shared("engine.ability.type.Resistance"))), std::pair(ability_t::IDLE, nyan::ValueHolder(std::make_shared("engine.ability.type.Idle"))), std::pair(ability_t::MOVE, diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index a1fed720e0..74b91da0d9 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -13,6 +13,7 @@ enum class ability_t { APPLY_DISCRETE_EFFECT, RANGED_CONTINUOUS_EFFECT, RANGED_DISCRETE_EFFECT, + RESISTANCE, IDLE, LIVE, MOVE, From 2e477cc70d4c950396dfb6f27b5a6b95c298ce1a Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:45:30 +0200 Subject: [PATCH 08/42] gamestate: API layer for nyan effects. --- libopenage/gamestate/api/CMakeLists.txt | 1 + libopenage/gamestate/api/definitions.h | 82 ++++++++++++++++++++++++- libopenage/gamestate/api/effect.cpp | 57 +++++++++++++++++ libopenage/gamestate/api/effect.h | 69 +++++++++++++++++++++ libopenage/gamestate/api/types.h | 29 +++++++++ 5 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 libopenage/gamestate/api/effect.cpp create mode 100644 libopenage/gamestate/api/effect.h diff --git a/libopenage/gamestate/api/CMakeLists.txt b/libopenage/gamestate/api/CMakeLists.txt index f3ad8d7b40..821164f2f5 100644 --- a/libopenage/gamestate/api/CMakeLists.txt +++ b/libopenage/gamestate/api/CMakeLists.txt @@ -3,6 +3,7 @@ add_sources(libopenage activity.cpp animation.cpp definitions.cpp + effect.cpp patch.cpp player_setup.cpp property.cpp diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index c48e56da7d..f9c8db6e30 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -45,7 +45,74 @@ static const auto ABILITY_DEFS = datastructure::create_const_map("engine.ability.type.Turn")))); /** - * Maps internal property types to nyan API values. + * Maps internal effect types to nyan API values. + */ +static const auto EFFECT_DEFS = datastructure::create_const_map( + std::pair(effect_t::CONTINUOUS_FLAC_DECREASE, + nyan::ValueHolder(std::make_shared( + "engine.effect.continuous.flat_attribute_change.type.FlatAttributeChangeDecrease"))), + std::pair(effect_t::CONTINUOUS_FLAC_INCREASE, + nyan::ValueHolder(std::make_shared( + "engine.effect.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease"))), + std::pair(effect_t::CONTINUOUS_LURE, + nyan::ValueHolder(std::make_shared("engine.effect.continuous.type.Lure"))), + std::pair(effect_t::CONTINUOUS_TRAC_DECREASE, + nyan::ValueHolder(std::make_shared( + "engine.effect.continuous.time_relative_attribute_change.type.TimeRelativeAttributeChangeDecrease"))), + std::pair(effect_t::CONTINUOUS_TRAC_INCREASE, + nyan::ValueHolder(std::make_shared( + "engine.effect.continuous.time_relative_attribute_change.type.TimeRelativeAttributeChangeIncrease"))), + std::pair(effect_t::CONTINUOUS_TRPC_DECREASE, + nyan::ValueHolder(std::make_shared( + "engine.effect.continuous.time_relative_progress_change.type.TimeRelativeProgressChangeDecrease"))), + std::pair(effect_t::CONTINUOUS_TRPC_INCREASE, + nyan::ValueHolder(std::make_shared( + "engine.effect.continuous.time_relative_progress_change.type.TimeRelativeProgressChangeIncrease"))), + std::pair(effect_t::DISCRETE_CONVERT, + nyan::ValueHolder(std::make_shared("engine.effect.discrete.Convert"))), + std::pair(effect_t::DISCRETE_FLAC_DECREASE, + nyan::ValueHolder(std::make_shared( + "engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease"))), + std::pair(effect_t::DISCRETE_FLAC_INCREASE, + nyan::ValueHolder(std::make_shared( + "engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeIncrease"))), + std::pair(effect_t::DISCRETE_MAKE_HARVESTABLE, + nyan::ValueHolder(std::make_shared("engine.effect.discrete.type.MakeHarvestable"))), + std::pair(effect_t::DISCRETE_SEND_TO_CONTAINER, + nyan::ValueHolder(std::make_shared("engine.effect.discrete.type.SendToContainer")))); + +/** + * Maps API effect types to internal effect types. + */ +static const auto EFFECT_TYPE_LOOKUP = datastructure::create_const_map( + std::pair("engine.effect.continuous.flat_attribute_change.type.FlatAttributeChangeDecrease", + effect_t::CONTINUOUS_FLAC_DECREASE), + std::pair("engine.effect.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease", + effect_t::CONTINUOUS_FLAC_INCREASE), + std::pair("engine.effect.continuous.type.Lure", + effect_t::CONTINUOUS_LURE), + std::pair("engine.effect.continuous.time_relative_attribute_change.type.TimeRelativeAttributeChangeDecrease", + effect_t::CONTINUOUS_TRAC_DECREASE), + std::pair("engine.effect.continuous.time_relative_attribute_change.type.TimeRelativeAttributeChangeIncrease", + effect_t::CONTINUOUS_TRAC_INCREASE), + std::pair("engine.effect.continuous.time_relative_progress_change.type.TimeRelativeProgressChangeDecrease", + effect_t::CONTINUOUS_TRPC_DECREASE), + std::pair("engine.effect.continuous.time_relative_progress_change.type.TimeRelativeProgressChangeIncrease", + effect_t::CONTINUOUS_TRPC_INCREASE), + std::pair("engine.effect.discrete.Convert", + effect_t::DISCRETE_CONVERT), + std::pair("engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease", + effect_t::DISCRETE_FLAC_DECREASE), + std::pair("engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeIncrease", + effect_t::DISCRETE_FLAC_INCREASE), + std::pair("engine.effect.discrete.type.MakeHarvestable", + effect_t::DISCRETE_MAKE_HARVESTABLE), + std::pair("engine.effect.discrete.type.SendToContainer", + effect_t::DISCRETE_SEND_TO_CONTAINER)); + + +/** + * Maps internal ability property types to nyan API values. */ static const auto ABILITY_PROPERTY_DEFS = datastructure::create_const_map( std::pair(ability_property_t::ANIMATED, @@ -61,6 +128,19 @@ static const auto ABILITY_PROPERTY_DEFS = datastructure::create_const_map("engine.ability.property.type.Lock")))); +/** + * Maps internal effect property types to nyan API values. + */ +static const auto EFFECT_PROPERTY_DEFS = datastructure::create_const_map( + std::pair(effect_property_t::AREA, + nyan::ValueHolder(std::make_shared("engine.effect.property.type.Area"))), + std::pair(effect_property_t::COST, + nyan::ValueHolder(std::make_shared("engine.effect.property.type.Cost"))), + std::pair(effect_property_t::DIPLOMATIC, + nyan::ValueHolder(std::make_shared("engine.effect.property.type.Diplomatic"))), + std::pair(effect_property_t::PRIORITY, + nyan::ValueHolder(std::make_shared("engine.effect.property.type.Priority")))); + /** * Maps API activity node types to engine node types. */ diff --git a/libopenage/gamestate/api/effect.cpp b/libopenage/gamestate/api/effect.cpp new file mode 100644 index 0000000000..691f97b58d --- /dev/null +++ b/libopenage/gamestate/api/effect.cpp @@ -0,0 +1,57 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "effect.h" + +#include "gamestate/api/definitions.h" + + +namespace openage::gamestate::api { + +bool APIEffect::is_effect(const nyan::Object &obj) { + for (auto &parent : obj.get_parents()) { + if (parent == "engine.effect.Effect") { + return true; + } + } + + return false; +} + +bool APIEffect::check_type(const nyan::Object &effect, + const effect_t &type) { + nyan::fqon_t immediate_parent = effect.get_parents()[0]; + nyan::ValueHolder effect_type = EFFECT_DEFS.get(type); + + std::shared_ptr effect_val = std::dynamic_pointer_cast( + effect_type.get_ptr()); + + return effect_val->get_name() == immediate_parent; +} + +bool APIEffect::check_property(const nyan::Object &effect, + const effect_property_t &property) { + std::shared_ptr properties = effect.get("Effect.properties"); + nyan::ValueHolder property_type = EFFECT_PROPERTY_DEFS.get(property); + + return properties->contains(property_type); +} + +effect_t APIEffect::get_type(const nyan::Object &effect) { + nyan::fqon_t immediate_parent = effect.get_parents()[0]; + + return EFFECT_TYPE_LOOKUP.get(immediate_parent); +} + +const nyan::Object APIEffect::get_property(const nyan::Object &effect, + const effect_property_t &property) { + std::shared_ptr properties = effect.get("Effect.properties"); + nyan::ValueHolder property_type = EFFECT_PROPERTY_DEFS.get(property); + + std::shared_ptr db_view = effect.get_view(); + std::shared_ptr property_val = std::dynamic_pointer_cast( + properties->get().at(property_type).get_ptr()); + + return db_view->get_object(property_val->get_name()); +} + +} // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/effect.h b/libopenage/gamestate/api/effect.h new file mode 100644 index 0000000000..33d1f0fcb6 --- /dev/null +++ b/libopenage/gamestate/api/effect.h @@ -0,0 +1,69 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "gamestate/api/types.h" + + +namespace openage::gamestate::api { + +/** + * Helper class for extracting values from Effect objects in the nyan API. + */ +class APIEffect { +public: + /** + * Check if a nyan object is an Effect (type == \p engine.effect.Effect). + * + * @param obj nyan object. + * + * @return true if the object is an effect, else false. + */ + static bool is_effect(const nyan::Object &obj); + + /** + * Check if an effect is of a given type. + * + * @param effect \p Effect nyan object (type == \p engine.effect.Effect). + * @param type Effect type. + * + * @return true if the effect is of the given type, else false. + */ + static bool check_type(const nyan::Object &effect, + const effect_t &type); + + /** + * Check if an effect has a given property. + * + * @param effect \p Effect nyan object (type == \p engine.effect.Effect). + * @param property Property type. + * + * @return true if the effect has the property, else false. + */ + static bool check_property(const nyan::Object &effect, + const effect_property_t &property); + + /** + * Get the type of an effect. + * + * @param effect \p Effect nyan object (type == \p engine.effect.Effect). + * + * @return Type of the effect. + */ + static effect_t get_type(const nyan::Object &effect); + + /** + * Get the nyan object for a property from an effect. + * + * @param effect \p Effect nyan object (type == \p engine.effect.Effect). + * @param property Property type. + * + * @return \p Property nyan object (type == \p engine.effect.property.Property). + */ + static const nyan::Object get_property(const nyan::Object &effect, + const effect_property_t &property); +}; + +} // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index 74b91da0d9..0b75ff1e49 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -22,6 +22,25 @@ enum class ability_t { // TODO }; +/** + * Types of effects for API objects. + */ +enum class effect_t { + CONTINUOUS_FLAC_DECREASE, + CONTINUOUS_FLAC_INCREASE, + CONTINUOUS_LURE, + CONTINUOUS_TRAC_DECREASE, + CONTINUOUS_TRAC_INCREASE, + CONTINUOUS_TRPC_DECREASE, + CONTINUOUS_TRPC_INCREASE, + + DISCRETE_CONVERT, + DISCRETE_FLAC_DECREASE, + DISCRETE_FLAC_INCREASE, + DISCRETE_MAKE_HARVESTABLE, + DISCRETE_SEND_TO_CONTAINER, +}; + /** * Types of properties for API abilities. */ @@ -34,6 +53,16 @@ enum class ability_property_t { LOCK, }; +/** + * Types of properties for API effects. + */ +enum class effect_property_t { + AREA, + COST, + DIPLOMATIC, + PRIORITY, +}; + /** * Types of properties for API patches. */ From f908c9cfd9b3362de750ad6476677be218519f15 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 04:29:14 +0200 Subject: [PATCH 09/42] gamestate: API layer for nyan resistances. --- libopenage/gamestate/api/CMakeLists.txt | 1 + libopenage/gamestate/api/definitions.h | 75 +++++++++++++++++++++++++ libopenage/gamestate/api/resistance.cpp | 57 +++++++++++++++++++ libopenage/gamestate/api/resistance.h | 69 +++++++++++++++++++++++ libopenage/gamestate/api/types.h | 8 +++ 5 files changed, 210 insertions(+) create mode 100644 libopenage/gamestate/api/resistance.cpp create mode 100644 libopenage/gamestate/api/resistance.h diff --git a/libopenage/gamestate/api/CMakeLists.txt b/libopenage/gamestate/api/CMakeLists.txt index 821164f2f5..c6735e759a 100644 --- a/libopenage/gamestate/api/CMakeLists.txt +++ b/libopenage/gamestate/api/CMakeLists.txt @@ -7,6 +7,7 @@ add_sources(libopenage patch.cpp player_setup.cpp property.cpp + resistance.cpp sound.cpp terrain.cpp types.cpp diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index f9c8db6e30..38667e919f 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -81,6 +81,43 @@ static const auto EFFECT_DEFS = datastructure::create_const_map("engine.effect.discrete.type.SendToContainer")))); +/** + * Maps internal effect types to nyan API values. + */ +static const auto RESISTANCE_DEFS = datastructure::create_const_map( + std::pair(effect_t::CONTINUOUS_FLAC_DECREASE, + nyan::ValueHolder(std::make_shared( + "engine.resistance.type.ContinuousFlatAttributeChangeDecrease"))), + std::pair(effect_t::CONTINUOUS_FLAC_INCREASE, + nyan::ValueHolder(std::make_shared( + "engine.resistance.type.ContinuousFlatAttributeChangeIncrease"))), + std::pair(effect_t::CONTINUOUS_LURE, + nyan::ValueHolder(std::make_shared("engine.resistance.type.Lure"))), + std::pair(effect_t::CONTINUOUS_TRAC_DECREASE, + nyan::ValueHolder(std::make_shared( + "engine.resistance.type.ContinuousTimeRelativeAttributeChangeDecrease"))), + std::pair(effect_t::CONTINUOUS_TRAC_INCREASE, + nyan::ValueHolder(std::make_shared( + "engine.resistance.type.ContinuousTimeRelativeAttributeChangeIncrease"))), + std::pair(effect_t::CONTINUOUS_TRPC_DECREASE, + nyan::ValueHolder(std::make_shared( + "engine.resistance.type.ContinuousTimeRelativeProgressChangeDecrease"))), + std::pair(effect_t::CONTINUOUS_TRPC_INCREASE, + nyan::ValueHolder(std::make_shared( + "engine.resistance.type.ContinuousTimeRelativeProgressChangeIncrease"))), + std::pair(effect_t::DISCRETE_CONVERT, + nyan::ValueHolder(std::make_shared("engine.resistance.type.Convert"))), + std::pair(effect_t::DISCRETE_FLAC_DECREASE, + nyan::ValueHolder(std::make_shared( + "engine.resistance.type.DiscreteFlatAttributeChangeDecrease"))), + std::pair(effect_t::DISCRETE_FLAC_INCREASE, + nyan::ValueHolder(std::make_shared( + "engine.resistance.type.DiscreteFlatAttributeChangeIncrease"))), + std::pair(effect_t::DISCRETE_MAKE_HARVESTABLE, + nyan::ValueHolder(std::make_shared("engine.resistance.type.MakeHarvestable"))), + std::pair(effect_t::DISCRETE_SEND_TO_CONTAINER, + nyan::ValueHolder(std::make_shared("engine.resistance.type.SendToContainer")))); + /** * Maps API effect types to internal effect types. */ @@ -110,6 +147,35 @@ static const auto EFFECT_TYPE_LOOKUP = datastructure::create_const_map( + std::pair("engine.resistance.type.ContinuousFlatAttributeChangeDecrease", + effect_t::CONTINUOUS_FLAC_DECREASE), + std::pair("engine.resistance.type.ContinuousFlatAttributeChangeIncrease", + effect_t::CONTINUOUS_FLAC_INCREASE), + std::pair("engine.resistance.type.Lure", + effect_t::CONTINUOUS_LURE), + std::pair("engine.resistance.type.ContinuousTimeRelativeAttributeChangeDecrease", + effect_t::CONTINUOUS_TRAC_DECREASE), + std::pair("engine.resistance.type.ContinuousTimeRelativeAttributeChangeIncrease", + effect_t::CONTINUOUS_TRAC_INCREASE), + std::pair("engine.resistance.type.ContinuousTimeRelativeProgressChangeDecrease", + effect_t::CONTINUOUS_TRPC_DECREASE), + std::pair("engine.resistance.type.ContinuousTimeRelativeProgressChangeIncrease", + effect_t::CONTINUOUS_TRPC_INCREASE), + std::pair("engine.resistance.type.Convert", + effect_t::DISCRETE_CONVERT), + std::pair("engine.resistance.type.DiscreteFlatAttributeChangeDecrease", + effect_t::DISCRETE_FLAC_DECREASE), + std::pair("engine.resistance.type.DiscreteFlatAttributeChangeIncrease", + effect_t::DISCRETE_FLAC_INCREASE), + std::pair("engine.resistance.type.MakeHarvestable", + effect_t::DISCRETE_MAKE_HARVESTABLE), + std::pair("engine.resistance.type.SendToContainer", + effect_t::DISCRETE_SEND_TO_CONTAINER)); + /** * Maps internal ability property types to nyan API values. @@ -141,6 +207,15 @@ static const auto EFFECT_PROPERTY_DEFS = datastructure::create_const_map("engine.effect.property.type.Priority")))); +/** + * Maps internal resistance property types to nyan API values. + */ +static const auto RESISTANCE_PROPERTY_DEFS = datastructure::create_const_map( + std::pair(resistance_property_t::COST, + nyan::ValueHolder(std::make_shared("engine.resistance.property.type.Cost"))), + std::pair(resistance_property_t::STACKED, + nyan::ValueHolder(std::make_shared("engine.resistance.property.type.Stacked")))); + /** * Maps API activity node types to engine node types. */ diff --git a/libopenage/gamestate/api/resistance.cpp b/libopenage/gamestate/api/resistance.cpp new file mode 100644 index 0000000000..60a93707e8 --- /dev/null +++ b/libopenage/gamestate/api/resistance.cpp @@ -0,0 +1,57 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "resistance.h" + +#include "gamestate/api/definitions.h" + + +namespace openage::gamestate::api { + +bool APIResistance::is_resistance(const nyan::Object &obj) { + for (auto &parent : obj.get_parents()) { + if (parent == "engine.resistance.Resistance") { + return true; + } + } + + return false; +} + +bool APIResistance::check_effect_type(const nyan::Object &resistance, + const effect_t &type) { + nyan::fqon_t immediate_parent = resistance.get_parents()[0]; + nyan::ValueHolder effect_type = RESISTANCE_DEFS.get(type); + + std::shared_ptr effect_val = std::dynamic_pointer_cast( + effect_type.get_ptr()); + + return effect_val->get_name() == immediate_parent; +} + +bool APIResistance::check_property(const nyan::Object &resistance, + const resistance_property_t &property) { + std::shared_ptr properties = resistance.get("Resistance.properties"); + nyan::ValueHolder property_type = RESISTANCE_PROPERTY_DEFS.get(property); + + return properties->contains(property_type); +} + +effect_t APIResistance::get_effect_type(const nyan::Object &resistance) { + nyan::fqon_t immediate_parent = resistance.get_parents()[0]; + + return RESISTANCE_TYPE_LOOKUP.get(immediate_parent); +} + +const nyan::Object APIResistance::get_property(const nyan::Object &resistance, + const resistance_property_t &property) { + std::shared_ptr properties = resistance.get("Resistance.properties"); + nyan::ValueHolder property_type = RESISTANCE_PROPERTY_DEFS.get(property); + + std::shared_ptr db_view = resistance.get_view(); + std::shared_ptr property_val = std::dynamic_pointer_cast( + properties->get().at(property_type).get_ptr()); + + return db_view->get_object(property_val->get_name()); +} + +} // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/resistance.h b/libopenage/gamestate/api/resistance.h new file mode 100644 index 0000000000..2f5ec70505 --- /dev/null +++ b/libopenage/gamestate/api/resistance.h @@ -0,0 +1,69 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "gamestate/api/types.h" + + +namespace openage::gamestate::api { + +/** + * Helper class for extracting values from Resistance objects in the nyan API. + */ +class APIResistance { +public: + /** + * Check if a nyan object is an Resistance (type == \p engine.resistance.Resistance). + * + * @param obj nyan object. + * + * @return true if the object is an resistance, else false. + */ + static bool is_resistance(const nyan::Object &obj); + + /** + * Check if an resistance matches a given effect type. + * + * @param resistance \p Resistance nyan object (type == \p engine.resistance.Resistance). + * @param type Effect type. + * + * @return true if the resistance is of the given type, else false. + */ + static bool check_effect_type(const nyan::Object &resistance, + const effect_t &type); + + /** + * Check if an resistance has a given property. + * + * @param resistance \p Resistance nyan object (type == \p engine.resistance.Resistance). + * @param property Property type. + * + * @return true if the resistance has the property, else false. + */ + static bool check_property(const nyan::Object &resistance, + const resistance_property_t &property); + + /** + * Get the matching effect type of a resistance. + * + * @param resistance \p Resistance nyan object (type == \p engine.resistance.Resistance). + * + * @return Type of the resistance. + */ + static effect_t get_effect_type(const nyan::Object &resistance); + + /** + * Get the nyan object for a property from an resistance. + * + * @param resistance \p Resistance nyan object (type == \p engine.resistance.Resistance). + * @param property Property type. + * + * @return \p Property nyan object (type == \p engine.resistance.property.Property). + */ + static const nyan::Object get_property(const nyan::Object &resistance, + const resistance_property_t &property); +}; + +} // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index 0b75ff1e49..50351696b0 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -63,6 +63,14 @@ enum class effect_property_t { PRIORITY, }; +/** + * Types of properties for API resistances. + */ +enum class resistance_property_t { + COST, + STACKED, +}; + /** * Types of properties for API patches. */ From 1d825d00139d2f62245997e866b310ead001b907 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 01:11:03 +0200 Subject: [PATCH 10/42] gamestate: Add LineOfSight component. --- .../gamestate/component/api/CMakeLists.txt | 1 + .../gamestate/component/api/line_of_sight.cpp | 12 ++++++++++++ .../gamestate/component/api/line_of_sight.h | 18 ++++++++++++++++++ libopenage/gamestate/component/types.h | 1 + libopenage/gamestate/entity_factory.cpp | 5 +++++ 5 files changed, 37 insertions(+) create mode 100644 libopenage/gamestate/component/api/line_of_sight.cpp create mode 100644 libopenage/gamestate/component/api/line_of_sight.h diff --git a/libopenage/gamestate/component/api/CMakeLists.txt b/libopenage/gamestate/component/api/CMakeLists.txt index 138c17fc0f..dcd5c9ba2f 100644 --- a/libopenage/gamestate/component/api/CMakeLists.txt +++ b/libopenage/gamestate/component/api/CMakeLists.txt @@ -1,6 +1,7 @@ add_sources(libopenage apply_effect.cpp idle.cpp + line_of_sight.cpp live.cpp move.cpp resistance.cpp diff --git a/libopenage/gamestate/component/api/line_of_sight.cpp b/libopenage/gamestate/component/api/line_of_sight.cpp new file mode 100644 index 0000000000..5960a65951 --- /dev/null +++ b/libopenage/gamestate/component/api/line_of_sight.cpp @@ -0,0 +1,12 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "line_of_sight.h" + + +namespace openage::gamestate::component { + +component_t LineOfSight::get_type() const { + return component_t::LINE_OF_SIGHT; +} + +} // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/api/line_of_sight.h b/libopenage/gamestate/component/api/line_of_sight.h new file mode 100644 index 0000000000..f2d937ed01 --- /dev/null +++ b/libopenage/gamestate/component/api/line_of_sight.h @@ -0,0 +1,18 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "gamestate/component/api_component.h" +#include "gamestate/component/types.h" + + +namespace openage::gamestate::component { + +class LineOfSight final : public APIComponent { +public: + using APIComponent::APIComponent; + + component_t get_type() const override; +}; + +} // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/types.h b/libopenage/gamestate/component/types.h index 4c2d62cdc6..00d921c82f 100644 --- a/libopenage/gamestate/component/types.h +++ b/libopenage/gamestate/component/types.h @@ -23,6 +23,7 @@ enum class component_t { MOVE, SELECTABLE, LIVE, + LINE_OF_SIGHT, }; } // namespace openage::gamestate::component diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 836ec281a1..24af06ee5a 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -25,6 +25,7 @@ #include "gamestate/api/activity.h" #include "gamestate/component/api/apply_effect.h" #include "gamestate/component/api/idle.h" +#include "gamestate/component/api/line_of_sight.h" #include "gamestate/component/api/live.h" #include "gamestate/component/api/move.h" #include "gamestate/component/api/resistance.h" @@ -221,6 +222,10 @@ void EntityFactory::init_components(const std::shared_ptr(loop, ability_obj); entity->add_component(resistance); } + else if (ability_parent == "engine.ability.type.LineOfSight") { + auto line_of_sight = std::make_shared(loop, ability_obj); + entity->add_component(line_of_sight); + } else { log::log(DBG << "Entity has unrecognized ability type: " << ability_parent); } From cb314867e9fd8c60736c33e934a3a91b88542aa7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 01:15:08 +0200 Subject: [PATCH 11/42] gamestate: LineOfSight definitions. --- libopenage/gamestate/api/definitions.h | 4 +++- libopenage/gamestate/api/types.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index 38667e919f..72c6d8d046 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -42,7 +42,9 @@ static const auto ABILITY_DEFS = datastructure::create_const_map("engine.ability.type.Live"))), std::pair(ability_t::TURN, - nyan::ValueHolder(std::make_shared("engine.ability.type.Turn")))); + nyan::ValueHolder(std::make_shared("engine.ability.type.Turn"))), + std::pair(ability_t::LINE_OF_SIGHT, + nyan::ValueHolder(std::make_shared("engine.ability.type.LineOfSight")))); /** * Maps internal effect types to nyan API values. diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index 50351696b0..96c39b017a 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -18,6 +18,7 @@ enum class ability_t { LIVE, MOVE, TURN, + LINE_OF_SIGHT, // TODO }; From c69e0c1d088aa2d88cb9305a6da498c2100d1dd0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 01:19:58 +0200 Subject: [PATCH 12/42] gamestate: Add missing definitions for already implemented abilities. --- libopenage/gamestate/api/definitions.h | 22 +++++++++++++--------- libopenage/gamestate/api/types.h | 10 ++++++---- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index 72c6d8d046..fdff30edd1 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -25,26 +25,30 @@ namespace openage::gamestate::api { * Maps internal ability types to nyan API values. */ static const auto ABILITY_DEFS = datastructure::create_const_map( + std::pair(ability_t::ACTIVITY, + nyan::ValueHolder(std::make_shared("engine.ability.type.Activity"))), std::pair(ability_t::APPLY_CONTINUOUS_EFFECT, nyan::ValueHolder(std::make_shared("engine.ability.type.ApplyContinuousEffect"))), std::pair(ability_t::APPLY_DISCRETE_EFFECT, nyan::ValueHolder(std::make_shared("engine.ability.type.ApplyDiscreteEffect"))), - std::pair(ability_t::RANGED_CONTINUOUS_EFFECT, - nyan::ValueHolder(std::make_shared("engine.ability.type.RangedContinuousEffect"))), - std::pair(ability_t::RANGED_DISCRETE_EFFECT, - nyan::ValueHolder(std::make_shared("engine.ability.type.RangedDiscreteEffect"))), - std::pair(ability_t::RESISTANCE, - nyan::ValueHolder(std::make_shared("engine.ability.type.Resistance"))), std::pair(ability_t::IDLE, nyan::ValueHolder(std::make_shared("engine.ability.type.Idle"))), std::pair(ability_t::MOVE, nyan::ValueHolder(std::make_shared("engine.ability.type.Move"))), + std::pair(ability_t::LINE_OF_SIGHT, + nyan::ValueHolder(std::make_shared("engine.ability.type.LineOfSight"))), std::pair(ability_t::LIVE, nyan::ValueHolder(std::make_shared("engine.ability.type.Live"))), + std::pair(ability_t::RANGED_CONTINUOUS_EFFECT, + nyan::ValueHolder(std::make_shared("engine.ability.type.RangedContinuousEffect"))), + std::pair(ability_t::RANGED_DISCRETE_EFFECT, + nyan::ValueHolder(std::make_shared("engine.ability.type.RangedDiscreteEffect"))), + std::pair(ability_t::RESISTANCE, + nyan::ValueHolder(std::make_shared("engine.ability.type.Resistance"))), + std::pair(ability_t::SELECTABLE, + nyan::ValueHolder(std::make_shared("engine.ability.type.Selectable"))), std::pair(ability_t::TURN, - nyan::ValueHolder(std::make_shared("engine.ability.type.Turn"))), - std::pair(ability_t::LINE_OF_SIGHT, - nyan::ValueHolder(std::make_shared("engine.ability.type.LineOfSight")))); + nyan::ValueHolder(std::make_shared("engine.ability.type.Turn")))); /** * Maps internal effect types to nyan API values. diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index 96c39b017a..7eeb141e71 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -9,16 +9,18 @@ namespace openage::gamestate::api { * Types of abilities for API objects. */ enum class ability_t { + ACTIVITY, APPLY_CONTINUOUS_EFFECT, APPLY_DISCRETE_EFFECT, - RANGED_CONTINUOUS_EFFECT, - RANGED_DISCRETE_EFFECT, - RESISTANCE, IDLE, + LINE_OF_SIGHT, LIVE, MOVE, + RANGED_CONTINUOUS_EFFECT, + RANGED_DISCRETE_EFFECT, + RESISTANCE, + SELECTABLE, TURN, - LINE_OF_SIGHT, // TODO }; From 01c2823c7e51340d301d4ca81904b386c996300e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 17:29:25 +0200 Subject: [PATCH 13/42] gamestate: Allow fractional values for attributes. --- libopenage/gamestate/component/api/live.cpp | 24 ++++++++++----------- libopenage/gamestate/component/api/live.h | 20 +++++++++++------ libopenage/gamestate/component/types.h | 7 ++++++ libopenage/gamestate/entity_factory.cpp | 11 +++++----- 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/libopenage/gamestate/component/api/live.cpp b/libopenage/gamestate/component/api/live.cpp index 01fafde5ea..202a56b664 100644 --- a/libopenage/gamestate/component/api/live.cpp +++ b/libopenage/gamestate/component/api/live.cpp @@ -1,12 +1,10 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #include "live.h" -#include - -#include "curve/discrete.h" #include "curve/iterator.h" #include "curve/map_filter_iterator.h" +#include "curve/segmented.h" #include "gamestate/component/types.h" @@ -18,20 +16,22 @@ component_t Live::get_type() const { void Live::add_attribute(const time::time_t &time, const nyan::fqon_t &attribute, - std::shared_ptr> starting_values) { - this->attribute_values.insert(time, attribute, starting_values); + std::shared_ptr> starting_values) { + this->attributes.insert(time, attribute, starting_values); } void Live::set_attribute(const time::time_t &time, const nyan::fqon_t &attribute, - int64_t value) { - auto attribute_value = this->attribute_values.at(time, attribute); - - if (attribute_value) { - (**attribute_value)->set_last(time, value); + attribute_value_t value) { + auto attribute_iterator = this->attributes.at(time, attribute); + if (attribute_iterator) { + auto attribute_curve = **attribute_iterator; + auto current_value = attribute_curve->get(time); + attribute_curve->set_last_jump(time, current_value, value); } else { - // TODO: fail here + throw Error(MSG(err) << "Attribute not found: " << attribute); } } + } // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/api/live.h b/libopenage/gamestate/component/api/live.h index 2e1f5e41d5..02f347e219 100644 --- a/libopenage/gamestate/component/api/live.h +++ b/libopenage/gamestate/component/api/live.h @@ -13,7 +13,14 @@ #include "time/time.h" -namespace openage::gamestate::component { +namespace openage { + +namespace curve { +template +class Segmented; +} // namespace curve + +namespace gamestate::component { class Live final : public APIComponent { public: using APIComponent::APIComponent; @@ -29,7 +36,7 @@ class Live final : public APIComponent { */ void add_attribute(const time::time_t &time, const nyan::fqon_t &attribute, - std::shared_ptr> starting_values); + std::shared_ptr> starting_values); /** * Set the value of an attribute at a given time. @@ -40,16 +47,17 @@ class Live final : public APIComponent { */ void set_attribute(const time::time_t &time, const nyan::fqon_t &attribute, - int64_t value); + attribute_value_t value); private: using attribute_storage_t = curve::UnorderedMap>>; + std::shared_ptr>>; /** * Map of attribute values by attribute type. */ - attribute_storage_t attribute_values; + attribute_storage_t attributes; }; -} // namespace openage::gamestate::component +} // namespace gamestate::component +} // namespace openage diff --git a/libopenage/gamestate/component/types.h b/libopenage/gamestate/component/types.h index 00d921c82f..0e117c7471 100644 --- a/libopenage/gamestate/component/types.h +++ b/libopenage/gamestate/component/types.h @@ -2,9 +2,16 @@ #pragma once +#include "util/fixed_point.h" + namespace openage::gamestate::component { +/** + * Type for attribute values. + */ +using attribute_value_t = util::FixedPoint; + /** * Types of components. */ diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 24af06ee5a..8d443a150f 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -197,11 +197,12 @@ void EntityFactory::init_components(const std::shared_ptradd_attribute(time::TIME_MIN, attribute.get_name(), - std::make_shared>(loop, - 0, - "", - nullptr, - start_value)); + std::make_shared>( + loop, + 0, + "", + nullptr, + start_value)); } } else if (ability_parent == "engine.ability.type.Activity") { From aafc6fbd88b602b2b880dd26a2d402d6bde14817 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 21:57:15 +0200 Subject: [PATCH 14/42] gamestate: Calculate application for discrete FLAC effects. --- libopenage/gamestate/system/apply_effect.cpp | 160 ++++++++++++++++++- libopenage/gamestate/system/apply_effect.h | 32 +++- 2 files changed, 184 insertions(+), 8 deletions(-) diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index 8beb3292c1..50df237dfc 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -2,15 +2,167 @@ #include "apply_effect.h" +#include + +#include "error/error.h" + +#include "gamestate/api/effect.h" +#include "gamestate/api/resistance.h" +#include "gamestate/api/types.h" +#include "gamestate/component/api/apply_effect.h" +#include "gamestate/component/api/live.h" +#include "gamestate/component/api/resistance.h" +#include "gamestate/component/types.h" +#include "gamestate/game_entity.h" +#include "gamestate/game_state.h" + namespace openage::gamestate::system { -const time::time_t ApplyEffect::apply_effect(const std::shared_ptr &entity, +const time::time_t ApplyEffect::apply_effect(const std::shared_ptr &effector, const std::shared_ptr &state, - const std::shared_ptr &target, + const std::shared_ptr &resistor, const time::time_t &start_time) { - // TODO: Implement - return start_time; + auto effects_component = std::dynamic_pointer_cast( + effector->get_component(component::component_t::APPLY_EFFECT)); + auto effect_ability = effects_component->get_ability(); + auto batches = effect_ability.get_set("ApplyEffect.batches"); + + auto resistance_component = std::dynamic_pointer_cast( + resistor->get_component(component::component_t::RESISTANCE)); + auto resistance_ability = resistance_component->get_ability(); + auto resistances_set = resistance_ability.get_set("Resistance.resistances"); + + auto live_component = std::dynamic_pointer_cast( + resistor->get_component(component::component_t::LIVE)); + + // Extract the effects from the ability + std::unordered_map> effects{}; + for (auto &batch : batches) { + std::shared_ptr batch_obj_val = std::dynamic_pointer_cast(batch.get_ptr()); + auto batch_obj = effect_ability.get_view()->get_object(batch_obj_val->get_name()); + auto batch_effects = batch_obj.get_set("EffectBatch.effects"); + + for (auto &batch_effect : batch_effects) { + std::shared_ptr effect_obj_val = std::dynamic_pointer_cast(batch_effect.get_ptr()); + auto effect_obj = effect_ability.get_view()->get_object(effect_obj_val->get_name()); + auto effect_type = api::APIEffect::get_type(effect_obj); + + if (effects.contains(effect_type)) { + effects.emplace(effect_type, std::vector{}); + } + + effects[effect_type].push_back(effect_obj); + } + } + + // Extract the resistances from the ability + std::unordered_map> resistances{}; + for (auto &resistance : resistances_set) { + std::shared_ptr resistance_obj_val = std::dynamic_pointer_cast(resistance.get_ptr()); + auto resistance_obj = resistance_ability.get_view()->get_object(resistance_obj_val->get_name()); + auto resistance_type = api::APIResistance::get_effect_type(resistance_obj); + + if (resistances.contains(resistance_type)) { + resistances.emplace(resistance_type, std::vector{}); + } + + resistances[resistance_type].push_back(resistance_obj); + } + + time::time_t end_time = start_time; + + // Check for matching effects and resistances + for (auto &effect : effects) { + auto effect_type = effect.first; + auto effect_objs = effect.second; + + if (!resistances.contains(effect_type)) { + continue; + } + + auto resistance_objs = resistances[effect_type]; + + switch (effect_type) { + case api::effect_t::DISCRETE_FLAC_DECREASE: + case api::effect_t::DISCRETE_FLAC_INCREASE: { + // TODO: Filter effects by AttributeChangeType + auto attribute_amount = effect_objs[0].get_object("FlatAttributeChange.change_value"); + auto attribute = attribute_amount.get_object("AttributeAmount.type"); + auto applied_value = get_applied_discrete_flac(effect_objs, resistance_objs); + + // TODO: Check if delay is necessary + auto delay = effect_ability.get_float("ApplyDiscreteEffect.application_delay"); + auto reload_time = effect_ability.get_float("ApplyDiscreteEffect.reload_time"); + end_time += delay + reload_time; + + // Record the time when the effects were applied + effects_component->set_init_time(start_time + delay); + effects_component->set_last_used(end_time); + + // Apply the effect to the live component + live_component->set_attribute(end_time, attribute.get_name(), applied_value); + } break; + default: + throw Error(MSG(err) << "Effect type not implemented: " << static_cast(effect_type)); + } + } + + return end_time; +} + + +const component::attribute_value_t ApplyEffect::get_applied_discrete_flac(const std::vector &effects, + const std::vector &resistances) { + component::attribute_value_t applied_value = 0; + component::attribute_value_t min_change = component::attribute_value_t::min_value(); + component::attribute_value_t max_change = component::attribute_value_t::max_value(); + + for (auto &effect : effects) { + auto change_amount = effect.get_object("FlatAttributeChange.value"); + auto min_change_amount = effect.get_optional("FlatAttributeChange.min_change_value"); + auto max_change_amount = effect.get_optional("FlatAttributeChange.max_change_value"); + + // Get value from change amount + // TODO: Ensure that the attribute is the same for all effects + auto change_value = change_amount.get_int("AttributeAmount.amount"); + applied_value += change_value; + + // TODO: The code below creates a clamp range from the lowest min and highest max values. + // This could create some uintended side effects where the clamped range is much larger + // than expected. Maybe this should be defined better. + + // Get min change value + if (min_change_amount) { + component::attribute_value_t min_change_value = (*min_change_amount)->get_int("AttributeAmount.amount"); + min_change = std::min(min_change_value, min_change); + } + + // Get max change value + if (max_change_amount) { + component::attribute_value_t max_change_value = (*max_change_amount)->get_int("AttributeAmount.amount"); + max_change = std::max(max_change_value, max_change); + } + } + + // TODO: Match effects to exactly one resistance to avoid multi resiatance. + // idea: move effect type to Effect object and make Resistance.resistances a dict. + + for (auto &resistance : resistances) { + auto block_amount = resistance.get_object("FlatAttributeChange.value"); + auto min_block_amount = resistance.get_optional("FlatAttributeChange.min_change_value"); + auto max_block_amount = resistance.get_optional("FlatAttributeChange.max_change_value"); + + // Get value from block amount + // TODO: Ensure that the attribute is the same attribute used in the effects + auto block_value = block_amount.get_int("AttributeAmount.amount"); + applied_value -= block_value; + } + + // Clamp the applied value + applied_value = std::clamp(applied_value, min_change, max_change); + + return applied_value; } } // namespace openage::gamestate::system diff --git a/libopenage/gamestate/system/apply_effect.h b/libopenage/gamestate/system/apply_effect.h index c00d7fbf78..d7b9f02450 100644 --- a/libopenage/gamestate/system/apply_effect.h +++ b/libopenage/gamestate/system/apply_effect.h @@ -2,6 +2,11 @@ #pragma once +#include + +#include + +#include "gamestate/component/types.h" #include "time/time.h" @@ -15,20 +20,39 @@ namespace system { class ApplyEffect { +public: /** * Apply the effect of an ability to a game entity. * - * @param entity Game entity applying the effects. + * @param effector Game entity applying the effects. * @param state Game state. - * @param target Target entity of the effects. + * @param resistor Target entity of the effects. * @param start_time Start time of change. * * @return Runtime of the change in simulation time. */ - static const time::time_t apply_effect(const std::shared_ptr &entity, + static const time::time_t apply_effect(const std::shared_ptr &effector, const std::shared_ptr &state, - const std::shared_ptr &target, + const std::shared_ptr &resistor, const time::time_t &start_time); + +private: + /** + * Get the gross applied value for discrete FlatAttributeChange effects. + * + * The gross applied value is calculated as follows: + * + * applied_value = clamp(change_value - block_value, min_change, max_change) + * + * Effects and resistances MUST have the same attribute change type. + * + * @param effects Effects of the effector. + * @param resistances Resistances of the resistor. + * + * @return Gross applied attribute change value. + */ + static const component::attribute_value_t get_applied_discrete_flac(const std::vector &effects, + const std::vector &resistances); }; } // namespace system From ad572b42e76d277b7f940de65d0d0f6f8d8bb345 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 13 Sep 2024 23:48:54 +0200 Subject: [PATCH 15/42] gamestate: Decrease log level of unrecognized components. --- libopenage/gamestate/entity_factory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 8d443a150f..6907e8aae9 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -228,7 +228,7 @@ void EntityFactory::init_components(const std::shared_ptradd_component(line_of_sight); } else { - log::log(DBG << "Entity has unrecognized ability type: " << ability_parent); + log::log(SPAM << "Entity has unrecognized ability type: " << ability_parent); } } From d98e69f3efb1a4880d8bbf9efd4e529e6094c443 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 14 Sep 2024 13:49:26 +0200 Subject: [PATCH 16/42] gamestate: ApplyEffect command. --- .../internal/commands/CMakeLists.txt | 1 + .../internal/commands/apply_effect.cpp | 15 +++++++ .../internal/commands/apply_effect.h | 43 +++++++++++++++++++ .../component/internal/commands/types.h | 3 +- 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 libopenage/gamestate/component/internal/commands/apply_effect.cpp create mode 100644 libopenage/gamestate/component/internal/commands/apply_effect.h diff --git a/libopenage/gamestate/component/internal/commands/CMakeLists.txt b/libopenage/gamestate/component/internal/commands/CMakeLists.txt index 8c6ec147b9..0bb7056083 100644 --- a/libopenage/gamestate/component/internal/commands/CMakeLists.txt +++ b/libopenage/gamestate/component/internal/commands/CMakeLists.txt @@ -1,4 +1,5 @@ add_sources(libopenage + apply_effect.cpp base_command.cpp custom.cpp idle.cpp diff --git a/libopenage/gamestate/component/internal/commands/apply_effect.cpp b/libopenage/gamestate/component/internal/commands/apply_effect.cpp new file mode 100644 index 0000000000..8c10d608a6 --- /dev/null +++ b/libopenage/gamestate/component/internal/commands/apply_effect.cpp @@ -0,0 +1,15 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "apply_effect.h" + + +namespace openage::gamestate::component::command { + +ApplyEffect::ApplyEffect(const gamestate::entity_id_t &target) : + target{target} {} + +const gamestate::entity_id_t &ApplyEffect::get_target() const { + return this->target; +} + +} // namespace openage::gamestate::component::command diff --git a/libopenage/gamestate/component/internal/commands/apply_effect.h b/libopenage/gamestate/component/internal/commands/apply_effect.h new file mode 100644 index 0000000000..e2a653f28f --- /dev/null +++ b/libopenage/gamestate/component/internal/commands/apply_effect.h @@ -0,0 +1,43 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "gamestate/component/internal/commands/base_command.h" +#include "gamestate/component/internal/commands/types.h" +#include "gamestate/types.h" + + +namespace openage::gamestate::component::command { + +/** + * Command for applying effects to a game entity. + */ +class ApplyEffect final : public Command { +public: + /** + * Creates a new apply effect command. + * + * @param target Target game entity ID. + */ + ApplyEffect(const gamestate::entity_id_t &target); + virtual ~ApplyEffect() = default; + + inline command_t get_type() const override { + return command_t::APPLY_EFFECT; + } + + /** + * Get the ID of the game entity targeted by the command. + * + * @return ID of the targeted game entity. + */ + const gamestate::entity_id_t &get_target() const; + +private: + /** + * Target position. + */ + const gamestate::entity_id_t target; +}; + +} // namespace openage::gamestate::component::command diff --git a/libopenage/gamestate/component/internal/commands/types.h b/libopenage/gamestate/component/internal/commands/types.h index 840f5eff22..6d6fe25095 100644 --- a/libopenage/gamestate/component/internal/commands/types.h +++ b/libopenage/gamestate/component/internal/commands/types.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once @@ -14,6 +14,7 @@ enum class command_t { CUSTOM, IDLE, MOVE, + APPLY_EFFECT, }; } // namespace openage::gamestate::component::command From 4b679c554e92ab0457349d8eca71ef1c94b07c47 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 14 Sep 2024 13:51:26 +0200 Subject: [PATCH 17/42] gamestate: Rename command classes and make them 'final'. --- .../gamestate/component/internal/commands/custom.cpp | 4 ++-- .../gamestate/component/internal/commands/custom.h | 6 +++--- .../gamestate/component/internal/commands/idle.h | 8 ++++---- .../gamestate/component/internal/commands/move.cpp | 6 +++--- .../gamestate/component/internal/commands/move.h | 6 +++--- libopenage/gamestate/event/send_command.cpp | 10 +++++----- libopenage/gamestate/system/move.cpp | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/libopenage/gamestate/component/internal/commands/custom.cpp b/libopenage/gamestate/component/internal/commands/custom.cpp index 600d19c38b..7780e0b6ed 100644 --- a/libopenage/gamestate/component/internal/commands/custom.cpp +++ b/libopenage/gamestate/component/internal/commands/custom.cpp @@ -1,11 +1,11 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "custom.h" namespace openage::gamestate::component::command { -CustomCommand::CustomCommand(const std::string &id) : +Custom::Custom(const std::string &id) : id{id} {} diff --git a/libopenage/gamestate/component/internal/commands/custom.h b/libopenage/gamestate/component/internal/commands/custom.h index 14b67f80a5..b199a099af 100644 --- a/libopenage/gamestate/component/internal/commands/custom.h +++ b/libopenage/gamestate/component/internal/commands/custom.h @@ -13,15 +13,15 @@ namespace openage::gamestate::component::command { /** * Custom command for everything that is not covered by the other commands. */ -class CustomCommand : public Command { +class Custom final : public Command { public: /** * Create a new custom command. * * @param id Command identifier. */ - CustomCommand(const std::string &id); - virtual ~CustomCommand() = default; + Custom(const std::string &id); + virtual ~Custom() = default; inline command_t get_type() const override { return command_t::CUSTOM; diff --git a/libopenage/gamestate/component/internal/commands/idle.h b/libopenage/gamestate/component/internal/commands/idle.h index 9870ad1ce5..821929cf28 100644 --- a/libopenage/gamestate/component/internal/commands/idle.h +++ b/libopenage/gamestate/component/internal/commands/idle.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once @@ -11,10 +11,10 @@ namespace openage::gamestate::component::command { /** * Command for idling (TODO: rename to Stop?). */ -class IdleCommand : public Command { +class Idle final : public Command { public: - IdleCommand() = default; - virtual ~IdleCommand() = default; + Idle() = default; + virtual ~Idle() = default; inline command_t get_type() const override { return command_t::IDLE; diff --git a/libopenage/gamestate/component/internal/commands/move.cpp b/libopenage/gamestate/component/internal/commands/move.cpp index 26242cea4e..adbcd105bf 100644 --- a/libopenage/gamestate/component/internal/commands/move.cpp +++ b/libopenage/gamestate/component/internal/commands/move.cpp @@ -1,14 +1,14 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "move.h" namespace openage::gamestate::component::command { -MoveCommand::MoveCommand(const coord::phys3 &target) : +Move::Move(const coord::phys3 &target) : target{target} {} -const coord::phys3 &MoveCommand::get_target() const { +const coord::phys3 &Move::get_target() const { return this->target; } diff --git a/libopenage/gamestate/component/internal/commands/move.h b/libopenage/gamestate/component/internal/commands/move.h index f550b546d3..eb07ec24e1 100644 --- a/libopenage/gamestate/component/internal/commands/move.h +++ b/libopenage/gamestate/component/internal/commands/move.h @@ -12,15 +12,15 @@ namespace openage::gamestate::component::command { /** * Command for moving to a target position. */ -class MoveCommand : public Command { +class Move final : public Command { public: /** * Creates a new move command. * * @param target Target position coordinates. */ - MoveCommand(const coord::phys3 &target); - virtual ~MoveCommand() = default; + Move(const coord::phys3 &target); + virtual ~Move() = default; inline command_t get_type() const override { return command_t::MOVE; diff --git a/libopenage/gamestate/event/send_command.cpp b/libopenage/gamestate/event/send_command.cpp index 8530d6340d..e01e7a5e77 100644 --- a/libopenage/gamestate/event/send_command.cpp +++ b/libopenage/gamestate/event/send_command.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "send_command.h" @@ -19,8 +19,8 @@ namespace component { class CommandQueue; namespace command { -class IdleCommand; -class MoveCommand; +class Idle; +class Move; } // namespace command } // namespace component @@ -65,12 +65,12 @@ void SendCommandHandler::invoke(openage::event::EventLoop & /* loop */, switch (command_type) { case component::command::command_t::IDLE: - command_queue->add_command(time, std::make_shared()); + command_queue->add_command(time, std::make_shared()); break; case component::command::command_t::MOVE: command_queue->add_command( time, - std::make_shared( + std::make_shared( params.get("target", coord::phys3{0, 0, 0}))); break; diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index 3da45ceab3..26fe094449 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -78,7 +78,7 @@ const time::time_t Move::move_command(const std::shared_ptr( entity->get_component(component::component_t::COMMANDQUEUE)); - auto command = std::dynamic_pointer_cast( + auto command = std::dynamic_pointer_cast( command_queue->pop_command(start_time)); if (not command) [[unlikely]] { From 142240f0e767888dae1b2cd87bf46b708c64aff1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 06:18:08 +0200 Subject: [PATCH 18/42] gamestate: Add condition for ApplyEffect command in activity system. --- .../gamestate/activity/condition/next_command.cpp | 15 ++++++++++++++- .../gamestate/activity/condition/next_command.h | 13 ++++++++++++- libopenage/gamestate/event/spawn_entity.cpp | 10 ++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/activity/condition/next_command.cpp b/libopenage/gamestate/activity/condition/next_command.cpp index c0b619a783..f7fb4b9e24 100644 --- a/libopenage/gamestate/activity/condition/next_command.cpp +++ b/libopenage/gamestate/activity/condition/next_command.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "next_command.h" @@ -34,4 +34,17 @@ bool next_command_move(const time::time_t &time, return command->get_type() == component::command::command_t::MOVE; } +bool next_command_apply_effect(const time::time_t &time, + const std::shared_ptr &entity) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + + if (command_queue->get_queue().empty(time)) { + return false; + } + + auto command = command_queue->get_queue().front(time); + return command->get_type() == component::command::command_t::APPLY_EFFECT; +} + } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/condition/next_command.h b/libopenage/gamestate/activity/condition/next_command.h index 046a18cec6..251cf9e243 100644 --- a/libopenage/gamestate/activity/condition/next_command.h +++ b/libopenage/gamestate/activity/condition/next_command.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once @@ -35,5 +35,16 @@ bool next_command_idle(const time::time_t &time, bool next_command_move(const time::time_t &time, const std::shared_ptr &entity); +/** + * Condition for next command check in the activity system. + * + * @param time Time when the condition is checked. + * @param entity Game entity. + * + * @return true if the entity has an apply effect command next in the queue, false otherwise. + */ +bool next_command_apply_effect(const time::time_t &time, + const std::shared_ptr &entity); + } // namespace activity } // namespace openage::gamestate diff --git a/libopenage/gamestate/event/spawn_entity.cpp b/libopenage/gamestate/event/spawn_entity.cpp index 66f6df9be7..4e665edaef 100644 --- a/libopenage/gamestate/event/spawn_entity.cpp +++ b/libopenage/gamestate/event/spawn_entity.cpp @@ -11,6 +11,7 @@ #include "coord/phys.h" #include "gamestate/component/internal/activity.h" #include "gamestate/component/internal/command_queue.h" +#include "gamestate/component/internal/commands/apply_effect.h" #include "gamestate/component/internal/ownership.h" #include "gamestate/component/internal/position.h" #include "gamestate/component/types.h" @@ -206,9 +207,18 @@ void SpawnEntityHandler::invoke(openage::event::EventLoop & /* loop */, entity->get_component(component::component_t::OWNERSHIP)); entity_owner->set_owner(time, owner_id); + // ASDF: Remove demo code below for applying effects + // add apply effect command to the command queue + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + auto apply_command = std::make_shared(entity->get_id()); + command_queue->add_command(time, apply_command); + auto activity = std::dynamic_pointer_cast( entity->get_component(component::component_t::ACTIVITY)); activity->init(time); + + // Important: Running the activity system must be done AFTER all components are initialized entity->get_manager()->run_activity_system(time); gstate->add_game_entity(entity); From 8ccd814eebcab02387a65381f2a3b807d16abc99 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:25:58 +0200 Subject: [PATCH 19/42] convert: Add new activity conditions for applying effects. --- .../conversion/aoc/pregen_processor.py | 45 +++++++++++++++++-- .../service/init/api_export_required.py | 2 +- .../convert/service/read/nyan_api_loader.py | 7 +++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index 48eb8b97e7..eb353b0857 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -98,6 +98,9 @@ def generate_activities( condition_parent = "engine.util.activity.condition.Condition" condition_queue_parent = "engine.util.activity.condition.type.CommandInQueue" condition_next_move_parent = "engine.util.activity.condition.type.NextCommandMove" + condition_next_apply_parent = ( + "engine.util.activity.condition.type.NextCommandApplyEffect" + ) # ======================================================================= # Default (Start -> Ability(Idle) -> End) @@ -276,10 +279,12 @@ def generate_activities( branch_raw_api_object.set_location(unit_forward_ref) branch_raw_api_object.add_raw_parent(xor_parent) - condition_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.NextCommandMove") + condition1_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.NextCommandMove") + condition2_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.NextCommandApplyEffect") branch_raw_api_object.add_raw_member("next", - [condition_forward_ref], + [condition1_forward_ref, condition2_forward_ref], xor_parent) idle_forward_ref = ForwardRef(pregen_converter_group, "util.activity.types.Unit.Idle") @@ -290,6 +295,40 @@ def generate_activities( pregen_converter_group.add_raw_api_object(branch_raw_api_object) pregen_nyan_objects.update({branch_ref_in_modpack: branch_raw_api_object}) + # condition for branching to apply effect + condition_ref_in_modpack = "util.activity.types.Unit.NextCommandApplyEffect" + condition_raw_api_object = RawAPIObject(condition_ref_in_modpack, + "NextCommandApplyEffect", api_objects) + condition_raw_api_object.set_location(branch_forward_ref) + condition_raw_api_object.add_raw_parent(condition_next_apply_parent) + + apply_effect_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.ApplyEffect") + condition_raw_api_object.add_raw_member("next", + apply_effect_forward_ref, + condition_parent) + + pregen_converter_group.add_raw_api_object(condition_raw_api_object) + pregen_nyan_objects.update({condition_ref_in_modpack: condition_raw_api_object}) + + # Apply effect + apply_effect_ref_in_modpack = "util.activity.types.Unit.ApplyEffect" + apply_effect_raw_api_object = RawAPIObject(apply_effect_ref_in_modpack, + "ApplyEffect", api_objects) + apply_effect_raw_api_object.set_location(unit_forward_ref) + apply_effect_raw_api_object.add_raw_parent(ability_parent) + + wait_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Wait") + apply_effect_raw_api_object.add_raw_member("next", wait_forward_ref, + ability_parent) + apply_effect_raw_api_object.add_raw_member("ability", + api_objects["engine.ability.type.ApplyDiscreteEffect"], + ability_parent) + + pregen_converter_group.add_raw_api_object(apply_effect_raw_api_object) + pregen_nyan_objects.update({apply_effect_ref_in_modpack: apply_effect_raw_api_object}) + # condition for branching to move condition_ref_in_modpack = "util.activity.types.Unit.NextCommandMove" condition_raw_api_object = RawAPIObject(condition_ref_in_modpack, diff --git a/openage/convert/service/init/api_export_required.py b/openage/convert/service/init/api_export_required.py index 8bb3a51850..8bb0e303cb 100644 --- a/openage/convert/service/init/api_export_required.py +++ b/openage/convert/service/init/api_export_required.py @@ -16,7 +16,7 @@ from openage.util.fslike.union import UnionPath -CURRENT_API_VERSION = "0.5.0" +CURRENT_API_VERSION = "0.6.0" def api_export_required(asset_dir: UnionPath) -> bool: diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index 9980b7fbf4..4a4396d425 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -546,6 +546,13 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.util.activity.condition.type.NextCommandApplyEffect + parents = [api_objects["engine.util.activity.condition.Condition"]] + nyan_object = NyanObject("NextCommandApplyEffect", parents) + fqon = "engine.util.activity.condition.type.NextCommandApplyEffect" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.util.activity.condition.type.NextCommandIdle parents = [api_objects["engine.util.activity.condition.Condition"]] nyan_object = NyanObject("NextCommandIdle", parents) From 4e58446ba93430676f1bd6c3a1a0a51a92a71b0a Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:27:03 +0200 Subject: [PATCH 20/42] gamestate: Handle ApplyEffect in activity system. --- libopenage/gamestate/api/definitions.h | 30 ++++++++++++-------- libopenage/gamestate/system/activity.cpp | 4 +++ libopenage/gamestate/system/apply_effect.cpp | 16 +++++------ libopenage/gamestate/system/types.h | 4 ++- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index fdff30edd1..ce4ee3e7a3 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -157,29 +157,31 @@ static const auto EFFECT_TYPE_LOOKUP = datastructure::create_const_map( - std::pair("engine.resistance.type.ContinuousFlatAttributeChangeDecrease", + std::pair("engine.resistance.continuous.flat_attribute_change.type.FlatAttributeChangeDecrease", effect_t::CONTINUOUS_FLAC_DECREASE), - std::pair("engine.resistance.type.ContinuousFlatAttributeChangeIncrease", + std::pair("engine.resistance.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease", effect_t::CONTINUOUS_FLAC_INCREASE), - std::pair("engine.resistance.type.Lure", + std::pair("engine.resistance.continuous.type.Lure", effect_t::CONTINUOUS_LURE), - std::pair("engine.resistance.type.ContinuousTimeRelativeAttributeChangeDecrease", + std::pair("engine.resistance.continuous.type.TimeRelativeAttributeChangeDecrease", effect_t::CONTINUOUS_TRAC_DECREASE), - std::pair("engine.resistance.type.ContinuousTimeRelativeAttributeChangeIncrease", + std::pair("engine.resistance.continuous.type.TimeRelativeAttributeChangeIncrease", effect_t::CONTINUOUS_TRAC_INCREASE), - std::pair("engine.resistance.type.ContinuousTimeRelativeProgressChangeDecrease", + std::pair("engine.resistance.continuous.type.TimeRelativeProgressChangeDecrease", effect_t::CONTINUOUS_TRPC_DECREASE), - std::pair("engine.resistance.type.ContinuousTimeRelativeProgressChangeIncrease", + std::pair("engine.resistance.continuous.type.TimeRelativeProgressChangeIncrease", effect_t::CONTINUOUS_TRPC_INCREASE), - std::pair("engine.resistance.type.Convert", + std::pair("engine.resistance.discrete.type.Convert", effect_t::DISCRETE_CONVERT), - std::pair("engine.resistance.type.DiscreteFlatAttributeChangeDecrease", + std::pair("engine.resistance.discrete.convert.type.AoE2Convert", // TODO: Remove from API + effect_t::DISCRETE_CONVERT), + std::pair("engine.resistance.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease", effect_t::DISCRETE_FLAC_DECREASE), - std::pair("engine.resistance.type.DiscreteFlatAttributeChangeIncrease", + std::pair("engine.resistance.discrete.flat_attribute_change.type.FlatAttributeChangeIncrease", effect_t::DISCRETE_FLAC_INCREASE), - std::pair("engine.resistance.type.MakeHarvestable", + std::pair("engine.resistance.discrete.type.MakeHarvestable", effect_t::DISCRETE_MAKE_HARVESTABLE), - std::pair("engine.resistance.type.SendToContainer", + std::pair("engine.resistance.discrete.type.SendToContainer", effect_t::DISCRETE_SEND_TO_CONTAINER)); @@ -243,6 +245,8 @@ static const auto ACTIVITY_NODE_DEFS = datastructure::create_const_map( + std::pair("engine.ability.type.ApplyDiscreteEffect", + system::system_id_t::APPLY_EFFECT), std::pair("engine.ability.type.Idle", system::system_id_t::IDLE), std::pair("engine.ability.type.Move", @@ -254,6 +258,8 @@ static const auto ACTIVITY_TASK_SYSTEM_DEFS = datastructure::create_const_map( std::pair("engine.util.activity.condition.type.CommandInQueue", std::function(gamestate::activity::command_in_queue)), + std::pair("engine.util.activity.condition.type.NextCommandApplyEffect", + std::function(gamestate::activity::next_command_apply_effect)), std::pair("engine.util.activity.condition.type.NextCommandIdle", std::function(gamestate::activity::next_command_idle)), std::pair("engine.util.activity.condition.type.NextCommandMove", diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index 7ef1d574b4..d5cb2da9d1 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.cpp @@ -19,6 +19,7 @@ #include "gamestate/component/internal/activity.h" #include "gamestate/component/types.h" #include "gamestate/game_entity.h" +#include "gamestate/system/apply_effect.h" #include "gamestate/system/idle.h" #include "gamestate/system/move.h" #include "util/fixed_point.h" @@ -125,6 +126,9 @@ const time::time_t Activity::handle_subsystem(const time::time_t &start_time, const std::shared_ptr &state, system_id_t system_id) { switch (system_id) { + case system_id_t::APPLY_EFFECT: + return ApplyEffect::apply_effect(entity, state, entity, start_time); + break; case system_id_t::IDLE: return Idle::idle(entity, start_time); break; diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index 50df237dfc..e4baf54628 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -26,7 +26,7 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptr( effector->get_component(component::component_t::APPLY_EFFECT)); auto effect_ability = effects_component->get_ability(); - auto batches = effect_ability.get_set("ApplyEffect.batches"); + auto batches = effect_ability.get_set("ApplyDiscreteEffect.batches"); auto resistance_component = std::dynamic_pointer_cast( resistor->get_component(component::component_t::RESISTANCE)); @@ -48,7 +48,7 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptrget_object(effect_obj_val->get_name()); auto effect_type = api::APIEffect::get_type(effect_obj); - if (effects.contains(effect_type)) { + if (not effects.contains(effect_type)) { effects.emplace(effect_type, std::vector{}); } @@ -63,7 +63,7 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptrget_object(resistance_obj_val->get_name()); auto resistance_type = api::APIResistance::get_effect_type(resistance_obj); - if (resistances.contains(resistance_type)) { + if (not resistances.contains(resistance_type)) { resistances.emplace(resistance_type, std::vector{}); } @@ -77,7 +77,7 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptr("FlatAttributeChange.min_change_value"); - auto max_change_amount = effect.get_optional("FlatAttributeChange.max_change_value"); + auto max_change_amount = effect.get_optional("max_change_value"); // Get value from change amount // TODO: Ensure that the attribute is the same for all effects @@ -149,9 +149,7 @@ const component::attribute_value_t ApplyEffect::get_applied_discrete_flac(const // idea: move effect type to Effect object and make Resistance.resistances a dict. for (auto &resistance : resistances) { - auto block_amount = resistance.get_object("FlatAttributeChange.value"); - auto min_block_amount = resistance.get_optional("FlatAttributeChange.min_change_value"); - auto max_block_amount = resistance.get_optional("FlatAttributeChange.max_change_value"); + auto block_amount = resistance.get_object("FlatAttributeChange.block_value"); // Get value from block amount // TODO: Ensure that the attribute is the same attribute used in the effects diff --git a/libopenage/gamestate/system/types.h b/libopenage/gamestate/system/types.h index 1da20da895..9930b47b9b 100644 --- a/libopenage/gamestate/system/types.h +++ b/libopenage/gamestate/system/types.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once @@ -11,6 +11,8 @@ namespace openage::gamestate::system { enum class system_id_t { NONE, + APPLY_EFFECT, + IDLE, MOVE_COMMAND, From ced1ee034582969e72bf9c47c0c91029db86b7c8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:38:11 +0200 Subject: [PATCH 21/42] gamestate: Fix time calculations for applying effects. --- libopenage/gamestate/system/apply_effect.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index e4baf54628..ce482dbba2 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -70,7 +70,12 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptrset_init_time(start_time + delay); - effects_component->set_last_used(end_time); + effects_component->set_last_used(start_time + total_time); // Apply the effect to the live component - live_component->set_attribute(end_time, attribute.get_name(), applied_value); + live_component->set_attribute(start_time + delay, attribute.get_name(), applied_value); } break; default: throw Error(MSG(err) << "Effect type not implemented: " << static_cast(effect_type)); } } - return end_time; + return total_time; } From fee671d0bdf908bf6f0fbf2dba8804a3a5c3d5c4 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:52:51 +0200 Subject: [PATCH 22/42] gamestate: Move animation property handling to helper function. --- libopenage/gamestate/system/CMakeLists.txt | 1 + libopenage/gamestate/system/idle.cpp | 15 +++------- libopenage/gamestate/system/move.cpp | 11 ++------ libopenage/gamestate/system/property.cpp | 32 ++++++++++++++++++++++ libopenage/gamestate/system/property.h | 31 +++++++++++++++++++++ 5 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 libopenage/gamestate/system/property.cpp create mode 100644 libopenage/gamestate/system/property.h diff --git a/libopenage/gamestate/system/CMakeLists.txt b/libopenage/gamestate/system/CMakeLists.txt index 389ddf1114..025729409a 100644 --- a/libopenage/gamestate/system/CMakeLists.txt +++ b/libopenage/gamestate/system/CMakeLists.txt @@ -3,5 +3,6 @@ add_sources(libopenage apply_effect.cpp idle.cpp move.cpp + property.cpp types.cpp ) diff --git a/libopenage/gamestate/system/idle.cpp b/libopenage/gamestate/system/idle.cpp index 3db9f5cb5f..aa883ebb5a 100644 --- a/libopenage/gamestate/system/idle.cpp +++ b/libopenage/gamestate/system/idle.cpp @@ -15,6 +15,7 @@ #include "gamestate/component/api/idle.h" #include "gamestate/component/types.h" #include "gamestate/game_entity.h" +#include "gamestate/system/property.h" namespace openage::gamestate::system { @@ -27,18 +28,10 @@ const time::time_t Idle::idle(const std::shared_ptr &enti auto idle_component = std::dynamic_pointer_cast( entity->get_component(component::component_t::IDLE)); - auto ability = idle_component->get_ability(); - if (api::APIAbility::check_property(ability, api::ability_property_t::ANIMATED)) { - auto property = api::APIAbility::get_property(ability, api::ability_property_t::ANIMATED); - auto animations = api::APIAbilityProperty::get_animations(property); - auto animation_paths = api::APIAnimation::get_animation_paths(animations); - - if (animation_paths.size() > 0) [[likely]] { - entity->render_update(start_time, animation_paths[0]); - } - } - // TODO: play sound + // properties + auto ability = idle_component->get_ability(); + handle_animated(entity, ability, start_time); return time::time_t::from_int(0); } diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index 26fe094449..d895eea88d 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -26,6 +26,7 @@ #include "gamestate/game_entity.h" #include "gamestate/game_state.h" #include "gamestate/map.h" +#include "gamestate/system/property.h" #include "pathfinding/path.h" #include "pathfinding/pathfinder.h" #include "util/fixed_point.h" @@ -171,15 +172,7 @@ const time::time_t Move::move_default(const std::shared_ptrget_ability(); - if (api::APIAbility::check_property(ability, api::ability_property_t::ANIMATED)) { - auto property = api::APIAbility::get_property(ability, api::ability_property_t::ANIMATED); - auto animations = api::APIAbilityProperty::get_animations(property); - auto animation_paths = api::APIAnimation::get_animation_paths(animations); - - if (animation_paths.size() > 0) [[likely]] { - entity->render_update(start_time, animation_paths[0]); - } - } + handle_animated(entity, ability, start_time); return total_time; } diff --git a/libopenage/gamestate/system/property.cpp b/libopenage/gamestate/system/property.cpp new file mode 100644 index 0000000000..339a65c879 --- /dev/null +++ b/libopenage/gamestate/system/property.cpp @@ -0,0 +1,32 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "property.h" + +#include "gamestate/api/ability.h" +#include "gamestate/api/animation.h" +#include "gamestate/api/property.h" +#include "gamestate/game_entity.h" + + +namespace openage::gamestate::system { + +bool handle_animated(const std::shared_ptr &entity, + const nyan::Object &ability, + const time::time_t &start_time) { + bool animated = api::APIAbility::check_property(ability, api::ability_property_t::ANIMATED); + + if (animated) { + auto property = api::APIAbility::get_property(ability, api::ability_property_t::ANIMATED); + auto animations = api::APIAbilityProperty::get_animations(property); + auto animation_paths = api::APIAnimation::get_animation_paths(animations); + + if (animation_paths.size() > 0) [[likely]] { + // TODO: More than one animation path + entity->render_update(start_time, animation_paths[0]); + } + } + + return animated; +} + +} // namespace openage::gamestate::system diff --git a/libopenage/gamestate/system/property.h b/libopenage/gamestate/system/property.h new file mode 100644 index 0000000000..e7c1cb4f99 --- /dev/null +++ b/libopenage/gamestate/system/property.h @@ -0,0 +1,31 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include + +#include "time/time.h" + + +namespace openage::gamestate { +class GameEntity; + +namespace system { + +/** + * Handle the animated property of an ability. + * + * @param entity Game entity. + * @param ability Ability object. + * @param start_time Start time of the animation. + * + * @return true if the ability has the property, false otherwise. + */ +bool handle_animated(const std::shared_ptr &entity, + const nyan::Object &ability, + const time::time_t &start_time); + +} // namespace system +} // namespace openage::gamestate From d508a5b63df0d7805afa77f8a38cd84e24dd9d88 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:53:09 +0200 Subject: [PATCH 23/42] gamestate: Animate effect application. --- libopenage/gamestate/system/apply_effect.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index ce482dbba2..f0f30b6e23 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -15,6 +15,7 @@ #include "gamestate/component/types.h" #include "gamestate/game_entity.h" #include "gamestate/game_state.h" +#include "gamestate/system/property.h" namespace openage::gamestate::system { @@ -108,6 +109,10 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptrget_ability(); + handle_animated(effector, ability, start_time); + return total_time; } From 27ad34414ab43da707e19be2eca740ae5bba600b Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 16 Oct 2024 07:39:09 +0200 Subject: [PATCH 24/42] curve: Add compress argument for curve operations. --- libopenage/curve/base_curve.h | 60 ++++++++++-- libopenage/curve/continuous.h | 16 ++- libopenage/curve/discrete_mod.h | 22 +++-- libopenage/curve/keyframe_container.h | 135 ++++++++++++++++++++------ 4 files changed, 186 insertions(+), 47 deletions(-) diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index dc58885b98..95fcea5052 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -73,24 +73,48 @@ 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. + * @param compress If true, only insert the keyframe if the value at time \p at + * is different from the given value. */ - virtual void set_replace(const time::time_t &at, const T &value); + virtual void set_replace(const time::time_t &at, + const T &value, + bool compress = false); /** * Remove all values that have the given time. @@ -112,9 +136,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 &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. @@ -129,11 +157,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 void sync(const BaseCurve &other, const std::function &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. @@ -199,7 +231,9 @@ class BaseCurve : public event::EventEntity { template -void BaseCurve::set_last(const time::time_t &at, const T &value) { +void BaseCurve::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 @@ -217,7 +251,9 @@ void BaseCurve::set_last(const time::time_t &at, const T &value) { template -void BaseCurve::set_insert(const time::time_t &at, const T &value) { +void BaseCurve::set_insert(const time::time_t &at, + const T &value, + bool compress) { 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()) { @@ -228,7 +264,9 @@ void BaseCurve::set_insert(const time::time_t &at, const T &value) { template -void BaseCurve::set_replace(const time::time_t &at, const T &value) { +void BaseCurve::set_replace(const time::time_t &at, + const T &value, + bool compress) { this->container.insert_overwrite(at, value, this->last_element); this->changes(at); } @@ -282,7 +320,8 @@ void BaseCurve::check_integrity() const { template void BaseCurve::sync(const BaseCurve &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); @@ -301,7 +340,8 @@ template template void BaseCurve::sync(const BaseCurve &other, const std::function &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); diff --git a/libopenage/curve/continuous.h b/libopenage/curve/continuous.h index 0cf438f237..d5c12aebb9 100644 --- a/libopenage/curve/continuous.h +++ b/libopenage/curve/continuous.h @@ -33,10 +33,14 @@ class Continuous : public Interpolated { * If multiple elements exist at the given time, * overwrite all of them. */ - void set_last(const time::time_t &t, const T &value) override; + void set_last(const time::time_t &t, + const T &value, + bool compress = false) override; /** This just calls set_replace in order to guarantee the continuity. */ - void set_insert(const time::time_t &t, const T &value) override; + void set_insert(const time::time_t &t, + const T &value, + bool compress = false) override; /** human readable identifier */ std::string idstr() const override; @@ -44,7 +48,9 @@ class Continuous : public Interpolated { template -void Continuous::set_last(const time::time_t &at, const T &value) { +void Continuous::set_last(const time::time_t &at, + const T &value, + bool compress) { auto hint = this->container.last(at, this->last_element); // erase all same-time entries @@ -62,7 +68,9 @@ void Continuous::set_last(const time::time_t &at, const T &value) { template -void Continuous::set_insert(const time::time_t &t, const T &value) { +void Continuous::set_insert(const time::time_t &t, + const T &value, + bool compress) { this->set_replace(t, value); } diff --git a/libopenage/curve/discrete_mod.h b/libopenage/curve/discrete_mod.h index 953939f975..ff8c7aa936 100644 --- a/libopenage/curve/discrete_mod.h +++ b/libopenage/curve/discrete_mod.h @@ -39,8 +39,12 @@ class DiscreteMod : public Discrete { // Override insertion/erasure to get interval time - 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 set_last(const time::time_t &at, + const T &value, + bool compress = false) override; + void set_insert(const time::time_t &at, + const T &value, + bool compress = false) override; void erase(const time::time_t &at) override; /** @@ -72,14 +76,18 @@ class DiscreteMod : public Discrete { template -void DiscreteMod::set_last(const time::time_t &at, const T &value) { +void DiscreteMod::set_last(const time::time_t &at, + const T &value, + bool compress) { BaseCurve::set_last(at, value); this->time_length = at; } template -void DiscreteMod::set_insert(const time::time_t &at, const T &value) { +void DiscreteMod::set_insert(const time::time_t &at, + const T &value, + bool compress) { BaseCurve::set_insert(at, value); if (this->time_length < at) { @@ -127,7 +135,8 @@ T DiscreteMod::get_mod(const time::time_t &time, const time::time_t &start) c template -std::pair DiscreteMod::get_time_mod(const time::time_t &time, const time::time_t &start) const { +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 @@ -140,7 +149,8 @@ std::pair DiscreteMod::get_time_mod(const time::time_t &time template -std::optional> DiscreteMod::get_previous_mod(const time::time_t &time, const time::time_t &start) const { +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 diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index d9bb9a0bbd..3a4b79e2a3 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -115,60 +115,111 @@ class KeyframeContainer { } /** - * Insert a new element without a hint. + * Insert a new element without submitting a hint. The search is + * started from the end of the data. * - * Starts the search for insertion at the end of the data. - * This function is not recommended for use, whenever possible, keep a hint - * to insert the data. + * The use of this function is discouraged, use it only, if your really + * do not have the possibility to get a hint. + * + * If there is a keyframe with identical time, this will + * insert the new keyframe before the old one. + * + * @param value Keyframe to insert. + * + * @return The location (index) of the inserted element. */ elem_ptr insert_before(const keyframe_t &value) { return this->insert_before(value, this->container.size()); } /** - * Insert a new element. The hint shall give an approximate location, where + * Insert a new element. + * + * The hint shall give an approximate location, where * the inserter will start to look for a insertion point. If a good hint is * given, the runtime of this function will not be affected by the current - * history size. If there is a keyframe with identical time, this will + * history size. + * + * If there is a keyframe with identical time, this will * insert the new keyframe before the old one. + * + * @param value Keyframe to insert. + * @param hint Index of the approximate insertion location. + * + * @return The location (index) of the inserted element. */ - elem_ptr insert_before(const keyframe_t &value, const elem_ptr &hint); + elem_ptr insert_before(const keyframe_t &value, + const elem_ptr &hint); /** - * Create and insert a new element without submitting a hint. The search is - * started 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. + * Create and insert a new element without submitting a hint. The search + * is started 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. + * + * If there is a keyframe with identical time, this will + * insert the new keyframe before the old one. + * + * @param time Time of the new keyframe. + * @param value Value of the new keyframe. + * + * @return The location (index) of the inserted element. */ - elem_ptr insert_before(const time::time_t &time, const T &value) { - return this->insert_before(keyframe_t(time, value), this->container.size()); + elem_ptr insert_before(const time::time_t &time, + const T &value) { + return this->insert_before(keyframe_t(time, value), + this->container.size()); } /** * Create and insert a new element. The hint gives an approximate location. + * * If there is a value with identical time, this will insert the new value * before the old one. + * + * @param time Time of the new keyframe. + * @param value Value of the new keyframe. + * @param hint Index of the approximate insertion location. + * + * @return The location (index) of the inserted element. */ - elem_ptr insert_before(const time::time_t &time, const T &value, const elem_ptr &hint) { + elem_ptr insert_before(const time::time_t &time, + const T &value, + const elem_ptr &hint) { return this->insert_before(keyframe_t(time, value), hint); } /** * Insert a new element, overwriting elements that have a - * time conflict. Give an approximate insertion location to minimize runtime + * time conflict. The hint gives an approximate insertion location to minimize runtime * on big-history curves. * `overwrite_all` == true -> overwrite all same-time elements. * `overwrite_all` == false -> overwrite the last of the time-conflict elements. + * + * @param value Keyframe to insert. + * @param hint Index of the approximate insertion location. + * @param overwrite_all If true, overwrite all elements with the same time. + * If false, overwrite only the last element with the same time. + * + * @return The location (index) of the inserted element. */ elem_ptr insert_overwrite(const keyframe_t &value, const elem_ptr &hint, bool overwrite_all = false); /** - * Insert a new value at given time which will overwrite the last of the + * Create and insert a new value at given time which will overwrite the last of the * elements with the same time. This function will start to search the 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. + * from the end of the data. + * + * The use of this function is discouraged, use itonly, if your really + * do not have the possibility to get a hint. + * + * @param time Time of the new keyframe. + * @param value Value of the new keyframe. + * + * @return The location (index) of the inserted element. */ elem_ptr insert_overwrite(const time::time_t &time, const T &value) { return this->insert_overwrite(keyframe_t(time, value), @@ -181,6 +232,14 @@ class KeyframeContainer { * element. If `overwrite_all` is true, overwrite all elements with same-time. * Provide a insertion hint to abbreviate the search for the * insertion point. + * + * @param time Time of the new keyframe. + * @param value Value of the new keyframe. + * @param hint Index of the approximate insertion location. + * @param overwrite_all If true, overwrite all elements with the same time. + * If false, overwrite only the last element with the same time. + * + * @return The location (index) of the inserted element. */ elem_ptr insert_overwrite(const time::time_t &time, const T &value, @@ -191,18 +250,32 @@ class KeyframeContainer { /** * Insert a new element, after a previous element when there's a time - * conflict. Give an approximate insertion location to minimize runtime on + * conflict. The hint gives an approximate insertion location to minimize runtime on * big-history curves. + * + * @param value Keyframe to insert. + * @param hint Index of the approximate insertion location. + * + * @return The location (index) of the inserted element. */ - elem_ptr insert_after(const keyframe_t &value, const elem_ptr &hint); + elem_ptr insert_after(const keyframe_t &value, + const elem_ptr &hint); /** - * Insert a new value at given time which will be prepended to the block of + * Create and insert a new value at given time which will be prepended to the block of * elements that have the same time. This function will start to search the - * 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. + * 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. + * + * @param time Time of the new keyframe. + * @param value Value of the new keyframe. + * + * @return The location (index) of the inserted element. */ - elem_ptr insert_after(const time::time_t &time, const T &value) { + elem_ptr insert_after(const time::time_t &time, + const T &value) { return this->insert_after(keyframe_t(time, value), this->container.size()); } @@ -210,8 +283,16 @@ 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. + * + * @param time Time of the new keyframe. + * @param value Value of the new keyframe. + * @param hint Index of the approximate insertion location. + * + * @return The location (index) of the inserted element. */ - elem_ptr insert_after(const time::time_t &time, const T &value, const elem_ptr &hint) { + elem_ptr insert_after(const time::time_t &time, + const T &value, + const elem_ptr &hint) { return this->insert_after(keyframe_t(time, value), hint); } @@ -279,8 +360,6 @@ class KeyframeContainer { * Replaces all keyframes beginning at t >= start with keyframes from \p other. * * @param other Curve that keyframes are copied from. - * @param converter Function that converts the value type of \p other to the - * value type of \p this. * @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. @@ -392,6 +471,8 @@ KeyframeContainer::last(const time::time_t &time, * * Intuitively, this function returns the element that comes right before the * first element that matches the search time. + * + * ASDF: Remove all comments for the implementations. */ template typename KeyframeContainer::elem_ptr From 2e4c56893b4cd4e6e59fa821300576cdb738ce8e Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 16 Oct 2024 07:45:57 +0200 Subject: [PATCH 25/42] curve: Rename argument for keyframes to 'keyframe'. Distinguishes it from the other methods that pass 'time' and 'value' to construct a new keyframe. --- libopenage/curve/keyframe_container.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index 3a4b79e2a3..52bc7cf25e 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -124,12 +124,12 @@ class KeyframeContainer { * If there is a keyframe with identical time, this will * insert the new keyframe before the old one. * - * @param value Keyframe to insert. + * @param keyframe Keyframe to insert. * * @return The location (index) of the inserted element. */ - elem_ptr insert_before(const keyframe_t &value) { - return this->insert_before(value, this->container.size()); + elem_ptr insert_before(const keyframe_t &keyframe) { + return this->insert_before(keyframe, this->container.size()); } /** @@ -143,12 +143,12 @@ class KeyframeContainer { * If there is a keyframe with identical time, this will * insert the new keyframe before the old one. * - * @param value Keyframe to insert. + * @param keyframe Keyframe to insert. * @param hint Index of the approximate insertion location. * * @return The location (index) of the inserted element. */ - elem_ptr insert_before(const keyframe_t &value, + elem_ptr insert_before(const keyframe_t &keyframe, const elem_ptr &hint); /** @@ -197,14 +197,14 @@ class KeyframeContainer { * `overwrite_all` == true -> overwrite all same-time elements. * `overwrite_all` == false -> overwrite the last of the time-conflict elements. * - * @param value Keyframe to insert. + * @param keyframe Keyframe to insert. * @param hint Index of the approximate insertion location. * @param overwrite_all If true, overwrite all elements with the same time. * If false, overwrite only the last element with the same time. * * @return The location (index) of the inserted element. */ - elem_ptr insert_overwrite(const keyframe_t &value, + elem_ptr insert_overwrite(const keyframe_t &keyframe, const elem_ptr &hint, bool overwrite_all = false); @@ -253,12 +253,12 @@ class KeyframeContainer { * conflict. The hint gives an approximate insertion location to minimize runtime on * big-history curves. * - * @param value Keyframe to insert. + * @param keyframe Keyframe to insert. * @param hint Index of the approximate insertion location. * * @return The location (index) of the inserted element. */ - elem_ptr insert_after(const keyframe_t &value, + elem_ptr insert_after(const keyframe_t &keyframe, const elem_ptr &hint); /** From 7b23eecc48426058305a82e23497567df493306c Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 16 Oct 2024 22:11:40 +0200 Subject: [PATCH 26/42] curve: Compress operation on keyframe insertion. --- libopenage/curve/base_curve.h | 23 +++++++++++++++++------ libopenage/event/demo/gamestate.h | 6 +++++- libopenage/main/demo/pong/gamestate.h | 6 +++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index 95fcea5052..b39093f26c 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -2,6 +2,7 @@ #pragma once +#include #include #include #include @@ -109,12 +110,9 @@ class BaseCurve : public event::EventEntity { * * @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_replace(const time::time_t &at, - const T &value, - bool compress = false); + const T &value); /** * Remove all values that have the given time. @@ -243,6 +241,13 @@ void BaseCurve::set_last(const time::time_t &at, 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; @@ -254,19 +259,25 @@ template void BaseCurve::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 void BaseCurve::set_replace(const time::time_t &at, - const T &value, - bool compress) { + const T &value) { this->container.insert_overwrite(at, value, this->last_element); this->changes(at); } diff --git a/libopenage/event/demo/gamestate.h b/libopenage/event/demo/gamestate.h index b92b22ee93..c4ba52c6e2 100644 --- a/libopenage/event/demo/gamestate.h +++ b/libopenage/event/demo/gamestate.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -45,6 +45,10 @@ class PongEvent { PongEvent() : player(0), state(IDLE) {} + bool operator==(const PongEvent &other) const { + return this->player == other.player and this->state == other.state; + } + size_t player; state_e state; }; diff --git a/libopenage/main/demo/pong/gamestate.h b/libopenage/main/demo/pong/gamestate.h index f66fbf1614..a56a7d7041 100644 --- a/libopenage/main/demo/pong/gamestate.h +++ b/libopenage/main/demo/pong/gamestate.h @@ -1,4 +1,4 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once @@ -36,6 +36,10 @@ class PongEvent { PongEvent() : player(0), state(IDLE) {} + bool operator==(const PongEvent &other) const { + return this->player == other.player && this->state == other.state; + } + size_t player; state_e state; }; From 486d6dfdf5832cb733bb714202bedc8de448da4d Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 18 Oct 2024 06:39:45 +0200 Subject: [PATCH 27/42] curve: Compress method for curves. --- libopenage/curve/base_curve.h | 11 +++++++++ libopenage/curve/discrete.h | 12 +++++++++ libopenage/curve/interpolated.h | 43 +++++++++++++++++++++++++++------ 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index b39093f26c..dd95685fe8 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -119,6 +119,17 @@ class BaseCurve : public event::EventEntity { */ 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. */ diff --git a/libopenage/curve/discrete.h b/libopenage/curve/discrete.h index 44fe132de6..14e2b74c78 100644 --- a/libopenage/curve/discrete.h +++ b/libopenage/curve/discrete.h @@ -34,6 +34,8 @@ class Discrete : public BaseCurve { */ T get(const time::time_t &t) const override; + void compress(const time::time_t &start = time::TIME_MIN) override; + /** * Get a human readable id string. */ @@ -58,6 +60,16 @@ T Discrete::get(const time::time_t &time) const { return this->container.get(e).val(); } +template +void Discrete::compress(const time::time_t &start) { + auto e = this->container.last_before(start, this->last_element); + + for (auto next = e + 1; next < this->container.size(); next++) { + if (this->container.get(next - 1).val() == this->container.get(next).val()) { + this->container.erase(next); + } + } +} template std::string Discrete::idstr() const { diff --git a/libopenage/curve/interpolated.h b/libopenage/curve/interpolated.h index 564ba1d0af..c5621218d7 100644 --- a/libopenage/curve/interpolated.h +++ b/libopenage/curve/interpolated.h @@ -35,6 +35,13 @@ class Interpolated : public BaseCurve { */ T get(const time::time_t &) const override; + + void compress(const time::time_t &start = time::TIME_MIN) override; + +private: + T interpolate(KeyframeContainer::elem_ptr before, + KeyframeContainer::elem_ptr after, + double elapsed) const; }; @@ -59,8 +66,8 @@ T Interpolated::get(const time::time_t &time) const { // If the next element is at the same time, just return the value of this one. if (nxt == this->container.size() // use the last curve value - || offset == 0 // values equal -> don't need to interpolate - || interval == 0) { // values at the same time -> division-by-zero-error + || offset == 0 // values equal -> don't need to interpolate + || interval == 0) { // values at the same time -> division-by-zero-error return this->container.get(e).val(); } @@ -69,13 +76,35 @@ T Interpolated::get(const time::time_t &time) const { // TODO: Elapsed time does not use fixed point arithmetic double elapsed_frac = offset.to_double() / interval.to_double(); - // TODO: nxt->value - e->value will produce wrong results if - // the nxt->value < e->value and curve element type is unsigned - // Example: nxt = 2, e = 4; type = uint8_t ==> 2 - 4 = 254 - auto diff_value = (this->container.get(nxt).val() - this->container.get(e).val()) * elapsed_frac; - return this->container.get(e).val() + diff_value; + return this->interpolate(e, nxt, elapsed_frac); } } +template +void Interpolated::compress(const time::time_t &start) { + auto e = this->container.last_before(start, this->last_element); + + for (auto current = e + 1; current < this->container.size() - 1; ++current) { + // TODO: Interpolate between current - 1 and current + 1, then check if + // the interpolated value is equal to the next value. + auto elapsed = this->container.get(current).time() - this->container.get(current - 1).time(); + auto interpolated = this->interpolate(current - 1, current + 1, elapsed.to_double()); + if (interpolated == this->container.get(current + 1).val()) { + this->container.erase(current); + } + } +} + +template +inline T Interpolated::interpolate(KeyframeContainer::elem_ptr before, + KeyframeContainer::elem_ptr after, + double elapsed) const { + // TODO: after->value - before->value will produce wrong results if + // after->value < before->value and curve element type is unsigned + // Example: after = 2, before = 4; type = uint8_t ==> 2 - 4 = 254 + auto diff_value = (this->container.get(after).val() - this->container.get(before).val()) * elapsed; + return this->container.get(before).val() + diff_value; +} + } // namespace openage::curve From 0c071ba9ad826ba053a65e9beddf6a92a9010e1a Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 18 Oct 2024 23:11:59 +0200 Subject: [PATCH 28/42] curve: Add new unit tests for compress() method. --- libopenage/curve/tests/curve_types.cpp | 76 +++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/libopenage/curve/tests/curve_types.cpp b/libopenage/curve/tests/curve_types.cpp index 421d3fb4d7..3a0ecb9b0e 100644 --- a/libopenage/curve/tests/curve_types.cpp +++ b/libopenage/curve/tests/curve_types.cpp @@ -232,7 +232,43 @@ void curve_types() { TESTEQUALS(c.get(8), 4); } - //Check the discrete type + { + // compression + auto f = std::make_shared(); + Continuous> c(f, 0); + c.set_insert(0, 0); + c.set_insert(1, 1); // redundant + c.set_insert(2, 2); // redundant + c.set_insert(3, 3); + c.set_insert(4, 3); // redundant + c.set_insert(5, 3); + c.set_insert(6, 4); + c.set_insert(7, 4); + + auto frame0 = c.frame(2); + TESTEQUALS(frame0.first, 2); + TESTEQUALS(frame0.second, 2); + TESTEQUALS(c.get(2), 2); + + auto frame1 = c.frame(4); + TESTEQUALS(frame1.first, 4); + TESTEQUALS(frame1.second, 3); + TESTEQUALS(c.get(4), 3); + + c.compress(0); + + auto frame2 = c.frame(2); + TESTEQUALS(frame2.first, 0); + TESTEQUALS(frame2.second, 0); + TESTEQUALS(c.get(2), 2); + + auto frame3 = c.frame(4); + TESTEQUALS(frame3.first, 3); + TESTEQUALS(frame3.second, 3); + TESTEQUALS(c.get(4), 3); + } + + // Check the discrete type { auto f = std::make_shared(); Discrete c(f, 0); @@ -257,7 +293,41 @@ void curve_types() { TESTEQUALS(complex.get(10), "Test 10"); } - //Check the discrete mod type + { + // compression + auto f = std::make_shared(); + Discrete c(f, 0); + c.set_insert(0, 1); + c.set_insert(1, 3); + c.set_insert(2, 3); // redundant + c.set_insert(3, 3); // redundant + c.set_insert(4, 4); + c.set_insert(5, 4); // redundant + + auto frame0 = c.frame(2); + TESTEQUALS(frame0.first, 2); + TESTEQUALS(frame0.second, 3); + TESTEQUALS(c.get(2), 3); + + auto frame1 = c.frame(5); + TESTEQUALS(frame1.first, 5); + TESTEQUALS(frame1.second, 4); + TESTEQUALS(c.get(5), 4); + + c.compress(0); + + auto frame2 = c.frame(2); + TESTEQUALS(frame2.first, 1); + TESTEQUALS(frame2.second, 3); + TESTEQUALS(c.get(2), 3); + + auto frame3 = c.frame(5); + TESTEQUALS(frame3.first, 4); + TESTEQUALS(frame3.second, 4); + TESTEQUALS(c.get(5), 4); + } + + // Check the discrete mod type { auto f = std::make_shared(); DiscreteMod c(f, 0); @@ -290,7 +360,7 @@ void curve_types() { TESTEQUALS(c.get_mod(15, 0), 0); } - //check set_last + // check set_last { auto f = std::make_shared(); Discrete c(f, 0); From b3df48a0c66681222940df60eb7937c4bc41eaf8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 18 Oct 2024 23:12:17 +0200 Subject: [PATCH 29/42] curve: Fix compression method. --- libopenage/curve/discrete.h | 23 +++++++++++++++++++--- libopenage/curve/interpolated.h | 35 +++++++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/libopenage/curve/discrete.h b/libopenage/curve/discrete.h index 14e2b74c78..153e0a7735 100644 --- a/libopenage/curve/discrete.h +++ b/libopenage/curve/discrete.h @@ -64,11 +64,28 @@ template void Discrete::compress(const time::time_t &start) { auto e = this->container.last_before(start, this->last_element); - for (auto next = e + 1; next < this->container.size(); next++) { - if (this->container.get(next - 1).val() == this->container.get(next).val()) { - this->container.erase(next); + // Store elements that should be kept + std::vector> to_keep; + auto last_kept = e; + for (auto current = e + 1; current < this->container.size(); ++current) { + if (this->container.get(last_kept).val() != this->container.get(current).val()) { + // Keep values that are different from the last kept value + to_keep.push_back(this->container.get(current)); + last_kept = current; } } + + // Erase all elements and insert the kept ones + this->container.erase_after(e); + for (auto &elem : to_keep) { + this->container.insert_after(elem, this->container.size() - 1); + } + + // Update the cached element pointer + this->last_element = e; + + // Notify observers about the changes + this->changes(start); } template diff --git a/libopenage/curve/interpolated.h b/libopenage/curve/interpolated.h index c5621218d7..c472403963 100644 --- a/libopenage/curve/interpolated.h +++ b/libopenage/curve/interpolated.h @@ -82,17 +82,40 @@ T Interpolated::get(const time::time_t &time) const { template void Interpolated::compress(const time::time_t &start) { + // Find the last element before the start time auto e = this->container.last_before(start, this->last_element); + // Store elements that should be kept + std::vector> to_keep; + auto last_kept = e; for (auto current = e + 1; current < this->container.size() - 1; ++current) { - // TODO: Interpolate between current - 1 and current + 1, then check if - // the interpolated value is equal to the next value. - auto elapsed = this->container.get(current).time() - this->container.get(current - 1).time(); - auto interpolated = this->interpolate(current - 1, current + 1, elapsed.to_double()); - if (interpolated == this->container.get(current + 1).val()) { - this->container.erase(current); + // offset is between current keyframe and the last kept keyframe + auto offset = this->container.get(current).time() - this->container.get(last_kept).time(); + auto interval = this->container.get(current + 1).time() - this->container.get(last_kept).time(); + auto elapsed_frac = offset.to_double() / interval.to_double(); + + // Interpolate the value that would be at the current keyframe (if it didn't exist) + auto interpolated = this->interpolate(last_kept, current + 1, elapsed_frac); + if (interpolated != this->container.get(current).val()) { + // Keep values that are different from the interpolated value + to_keep.push_back(this->container.get(current)); + last_kept = current; } } + // The last element is always kept, so we have to add it manually to keep it + to_keep.push_back(this->container.get(this->container.size() - 1)); + + // Erase all old keyframes after start and reinsert the non-redundant keyframes + this->container.erase_after(e); + for (auto elem : to_keep) { + this->container.insert_after(elem, this->container.size() - 1); + } + + // Update the cached element pointer + this->last_element = e; + + // Notify observers about the changes + this->changes(start); } template From bbb697fb8cd373bd0ee010ced3c4de7b705f1333 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 19 Oct 2024 22:04:39 +0200 Subject: [PATCH 30/42] curve: Pass through compression args. --- libopenage/curve/continuous.h | 9 ++++++++- libopenage/curve/discrete_mod.h | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libopenage/curve/continuous.h b/libopenage/curve/continuous.h index d5c12aebb9..92f06053d8 100644 --- a/libopenage/curve/continuous.h +++ b/libopenage/curve/continuous.h @@ -60,6 +60,13 @@ void Continuous::set_last(const time::time_t &at, 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; @@ -70,7 +77,7 @@ void Continuous::set_last(const time::time_t &at, template void Continuous::set_insert(const time::time_t &t, const T &value, - bool compress) { + bool /* compress */) { this->set_replace(t, value); } diff --git a/libopenage/curve/discrete_mod.h b/libopenage/curve/discrete_mod.h index ff8c7aa936..4ff51437af 100644 --- a/libopenage/curve/discrete_mod.h +++ b/libopenage/curve/discrete_mod.h @@ -79,7 +79,7 @@ template void DiscreteMod::set_last(const time::time_t &at, const T &value, bool compress) { - BaseCurve::set_last(at, value); + BaseCurve::set_last(at, value, compress); this->time_length = at; } @@ -88,7 +88,7 @@ template void DiscreteMod::set_insert(const time::time_t &at, const T &value, bool compress) { - BaseCurve::set_insert(at, value); + BaseCurve::set_insert(at, value, compress); if (this->time_length < at) { this->time_length = at; From ba2c3ad80fd9b85d1cc1a241168275b151b6ab41 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 19 Oct 2024 22:09:14 +0200 Subject: [PATCH 31/42] curve: Compress during curve sync. --- libopenage/curve/base_curve.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index dd95685fe8..fc82d1bb82 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -354,6 +354,10 @@ void BaseCurve::sync(const BaseCurve &other, this->set_insert(start, get_other); } + if (compress) { + this->compress(start); + } + this->changes(start); } @@ -374,6 +378,10 @@ void BaseCurve::sync(const BaseCurve &other, this->set_insert(start, get_other); } + if (compress) { + this->compress(start); + } + this->changes(start); } From c0170748bcf12507403eab06c34313b10c2e72e5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 00:23:26 +0200 Subject: [PATCH 32/42] renderer: Compress sync on animations curve. --- libopenage/renderer/stages/world/object.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp index 2396c94ed2..32b645a432 100644 --- a/libopenage/renderer/stages/world/object.cpp +++ b/libopenage/renderer/stages/world/object.cpp @@ -66,7 +66,7 @@ void WorldObject::fetch_updates(const time::time_t &time) { // Thread-safe access to curves needs a lock on the render entity's mutex auto read_lock = this->render_entity->get_read_lock(); - this->position.sync(this->render_entity->get_position()); + this->position.sync(this->render_entity->get_position(), this->last_update); this->animation_info.sync(this->render_entity->get_animation_path(), std::function(const std::string &)>( [&](const std::string &path) { @@ -79,7 +79,8 @@ void WorldObject::fetch_updates(const time::time_t &time) { } return this->asset_manager->request_animation(path); }), - this->last_update); + this->last_update, + true); this->angle.sync(this->render_entity->get_angle(), this->last_update); // Unlock mutex of the render entity From 6073e25ba029e43c5eec686179fde82dc962920c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 15:04:23 +0200 Subject: [PATCH 33/42] renderer: Make fetching from render entity more reliable. --- libopenage/curve/keyframe_container.h | 23 +++++++------ libopenage/gamestate/system/idle.cpp | 2 +- libopenage/renderer/stages/hud/object.cpp | 11 +++--- .../renderer/stages/hud/render_entity.cpp | 5 +-- .../renderer/stages/hud/render_entity.h | 5 --- libopenage/renderer/stages/render_entity.cpp | 20 +++++------ libopenage/renderer/stages/render_entity.h | 34 +++++++++++++++---- libopenage/renderer/stages/terrain/chunk.cpp | 5 ++- .../renderer/stages/terrain/render_entity.cpp | 11 ++---- .../renderer/stages/terrain/render_entity.h | 10 +----- libopenage/renderer/stages/world/object.cpp | 23 +++++++------ .../renderer/stages/world/render_entity.cpp | 28 ++++++++------- .../renderer/stages/world/render_entity.h | 11 ------ 13 files changed, 90 insertions(+), 98 deletions(-) diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index 52bc7cf25e..e8987aab4b 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -607,18 +607,18 @@ template typename KeyframeContainer::elem_ptr KeyframeContainer::sync(const KeyframeContainer &other, const time::time_t &start) { - // Delete elements after start time + // Delete elements from this container after start time elem_ptr at = this->last_before(start, this->container.size()); at = this->erase_after(at); - auto at_other = 1; // always skip the first element (because it's the default value) + // Find the last element before the start time in the other container + elem_ptr at_other = other.last_before(start, other.size()); + ++at_other; // move one element forward so that at_other.time() >= start // Copy all elements from other with time >= start - for (size_t i = at_other; i < other.size(); i++) { - if (other.get(i).time() >= start) { - at = this->insert_after(other.get(i), at); - } - } + this->container.insert(this->container.end(), + other.container.begin() + at_other, + other.container.end()); return this->container.size(); } @@ -634,13 +634,14 @@ KeyframeContainer::sync(const KeyframeContainer &other, elem_ptr at = this->last_before(start, this->container.size()); at = this->erase_after(at); - auto at_other = 1; // always skip the first element (because it's the default value) + // Find the last element before the start time in the other container + elem_ptr at_other = other.last_before(start, other.size()); + ++at_other; // move one element forward so that at_other.time() >= start // Copy all elements from other with time >= start for (size_t i = at_other; i < other.size(); i++) { - if (other.get(i).time() >= start) { - at = this->insert_after(keyframe_t(other.get(i).time(), converter(other.get(i).val())), at); - } + auto &elem = other.get(i); + this->container.emplace_back(elem.time(), converter(elem.val())); } return this->container.size(); diff --git a/libopenage/gamestate/system/idle.cpp b/libopenage/gamestate/system/idle.cpp index aa883ebb5a..256ef7389e 100644 --- a/libopenage/gamestate/system/idle.cpp +++ b/libopenage/gamestate/system/idle.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "idle.h" diff --git a/libopenage/renderer/stages/hud/object.cpp b/libopenage/renderer/stages/hud/object.cpp index 6e23b6663a..2a69cdb91c 100644 --- a/libopenage/renderer/stages/hud/object.cpp +++ b/libopenage/renderer/stages/hud/object.cpp @@ -36,20 +36,17 @@ void HudDragObject::fetch_updates(const time::time_t &time) { return; } - // Get data from render entity - this->drag_start = this->render_entity->get_drag_start(); - // Thread-safe access to curves needs a lock on the render entity's mutex auto read_lock = this->render_entity->get_read_lock(); - this->drag_pos.sync(this->render_entity->get_drag_pos() /* , this->last_update */); + // Get data from render entity + this->drag_start = this->render_entity->get_drag_start(); - // Unlock the render entity mutex - read_lock.unlock(); + this->drag_pos.sync(this->render_entity->get_drag_pos() /* , this->last_update */); // Set self to changed so that world renderer can update the renderable this->changed = true; - this->render_entity->clear_changed_flag(); + this->render_entity->fetch_done(); this->last_update = time; } diff --git a/libopenage/renderer/stages/hud/render_entity.cpp b/libopenage/renderer/stages/hud/render_entity.cpp index 2ff3f75649..b3f65a70a8 100644 --- a/libopenage/renderer/stages/hud/render_entity.cpp +++ b/libopenage/renderer/stages/hud/render_entity.cpp @@ -20,18 +20,15 @@ void DragRenderEntity::update(const coord::input drag_pos, this->drag_pos.set_insert(time, drag_pos); this->last_update = time; + this->fetch_time = time; this->changed = true; } const curve::Continuous &DragRenderEntity::get_drag_pos() { - std::shared_lock lock{this->mutex}; - return this->drag_pos; } const coord::input DragRenderEntity::get_drag_start() { - std::shared_lock lock{this->mutex}; - return this->drag_start; } diff --git a/libopenage/renderer/stages/hud/render_entity.h b/libopenage/renderer/stages/hud/render_entity.h index 9d15204386..2a48e2d25d 100644 --- a/libopenage/renderer/stages/hud/render_entity.h +++ b/libopenage/renderer/stages/hud/render_entity.h @@ -39,9 +39,6 @@ class DragRenderEntity final : public renderer::RenderEntity { /** * Get the position of the dragged corner. * - * Accessing the drag position curve REQUIRES a read lock on the render entity - * (using \p get_read_lock()) to ensure thread safety. - * * @return Coordinates of the dragged corner. */ const curve::Continuous &get_drag_pos(); @@ -49,8 +46,6 @@ class DragRenderEntity final : public renderer::RenderEntity { /** * Get the position of the start corner. * - * Accessing the drag start is thread-safe. - * * @return Coordinates of the start corner. */ const coord::input get_drag_start(); diff --git a/libopenage/renderer/stages/render_entity.cpp b/libopenage/renderer/stages/render_entity.cpp index dc95de9a6d..77ed3ca2fc 100644 --- a/libopenage/renderer/stages/render_entity.cpp +++ b/libopenage/renderer/stages/render_entity.cpp @@ -2,20 +2,17 @@ #include "render_entity.h" -#include - namespace openage::renderer { RenderEntity::RenderEntity() : changed{false}, - last_update{time::time_t::zero()} { + last_update{time::TIME_ZERO}, + fetch_time{time::TIME_MAX} { } -time::time_t RenderEntity::get_update_time() { - std::shared_lock lock{this->mutex}; - - return this->last_update; +time::time_t RenderEntity::get_fetch_time() { + return this->fetch_time; } bool RenderEntity::is_changed() { @@ -24,14 +21,13 @@ bool RenderEntity::is_changed() { return this->changed; } -void RenderEntity::clear_changed_flag() { - std::unique_lock lock{this->mutex}; - +void RenderEntity::fetch_done() { this->changed = false; + this->fetch_time = time::TIME_MAX; } -std::shared_lock RenderEntity::get_read_lock() { - return std::shared_lock{this->mutex}; +std::unique_lock RenderEntity::get_read_lock() { + return std::unique_lock{this->mutex}; } } // namespace openage::renderer diff --git a/libopenage/renderer/stages/render_entity.h b/libopenage/renderer/stages/render_entity.h index f441452968..bd8535c9a5 100644 --- a/libopenage/renderer/stages/render_entity.h +++ b/libopenage/renderer/stages/render_entity.h @@ -15,32 +15,47 @@ namespace openage::renderer { /** * Interface for render entities that allow pushing updates from game simulation * to renderer. + * + * Accessing the render entity from the renderer thread REQUIRES a + * read lock on the render entity (using \p get_read_lock()) to ensure + * thread safety. */ class RenderEntity { public: ~RenderEntity() = default; /** - * Get the time of the last update. + * Get the earliest time for which updates are available. + * + * Render objects should synchronize their state with the render entity + * from this time onwards. * * Accessing the update time is thread-safe. * * @return Time of last update. */ - time::time_t get_update_time(); + time::time_t get_fetch_time(); /** * Check whether the render entity has received new updates from the * gamestate. * + * Accessing the change flag is thread-safe. + * * @return true if updates have been received, else false. */ bool is_changed(); /** - * Clear the update flag by setting it to false. + * Indicate to this entity that its updates have been processed and transfered to the + * render object. + * + * - Clear the update flag by setting it to false. + * - Sets the fetch time to \p time::MAX_TIME. + * + * Accessing this method is thread-safe. */ - void clear_changed_flag(); + void fetch_done(); /** * Get a shared lock for thread-safe reading from the render entity. @@ -49,7 +64,7 @@ class RenderEntity { * * @return Lock for the render entity. */ - std::shared_lock get_read_lock(); + std::unique_lock get_read_lock(); protected: /** @@ -71,10 +86,17 @@ class RenderEntity { bool changed; /** - * Time of the last update call. + * Time of the last update. */ time::time_t last_update; + /** + * Earliest time for which updates have been received. + * + * \p time::TIME_MAX indicates that no updates are available. + */ + time::time_t fetch_time; + /** * Mutex for protecting threaded access. */ diff --git a/libopenage/renderer/stages/terrain/chunk.cpp b/libopenage/renderer/stages/terrain/chunk.cpp index 58a95d2a95..db6b58dab8 100644 --- a/libopenage/renderer/stages/terrain/chunk.cpp +++ b/libopenage/renderer/stages/terrain/chunk.cpp @@ -32,6 +32,9 @@ void TerrainChunk::fetch_updates(const time::time_t & /* time */) { return; } + // Thread-safe access to data needs a lock on the render entity's mutex + auto read_lock = this->render_entity->get_read_lock(); + // Get the terrain data from the render entity auto terrain_size = this->render_entity->get_size(); auto terrain_paths = this->render_entity->get_terrain_paths(); @@ -54,7 +57,7 @@ void TerrainChunk::fetch_updates(const time::time_t & /* time */) { // this->meshes.push_back(new_mesh); // Indicate to the render entity that its updates have been processed. - this->render_entity->clear_changed_flag(); + this->render_entity->fetch_done(); } void TerrainChunk::update_uniforms(const time::time_t &time) { diff --git a/libopenage/renderer/stages/terrain/render_entity.cpp b/libopenage/renderer/stages/terrain/render_entity.cpp index 2316f5dab7..844773e777 100644 --- a/libopenage/renderer/stages/terrain/render_entity.cpp +++ b/libopenage/renderer/stages/terrain/render_entity.cpp @@ -42,6 +42,7 @@ void RenderEntity::update_tile(const util::Vector2s size, // update the last update time this->last_update = time; + this->fetch_time = time; // update the terrain paths this->terrain_paths.insert(terrain_path); @@ -94,7 +95,7 @@ void RenderEntity::update(const util::Vector2s size, this->tiles = tiles; // update the last update time - this->last_update = time; + this->fetch_time = time; // update the terrain paths this->terrain_paths.clear(); @@ -106,26 +107,18 @@ void RenderEntity::update(const util::Vector2s size, } const std::vector RenderEntity::get_vertices() { - std::shared_lock lock{this->mutex}; - return this->vertices; } const RenderEntity::tiles_t RenderEntity::get_tiles() { - std::shared_lock lock{this->mutex}; - return this->tiles; } const std::unordered_set RenderEntity::get_terrain_paths() { - std::shared_lock lock{this->mutex}; - return this->terrain_paths; } const util::Vector2s RenderEntity::get_size() { - std::shared_lock lock{this->mutex}; - return this->size; } diff --git a/libopenage/renderer/stages/terrain/render_entity.h b/libopenage/renderer/stages/terrain/render_entity.h index 0f726a2351..bd0867a6d2 100644 --- a/libopenage/renderer/stages/terrain/render_entity.h +++ b/libopenage/renderer/stages/terrain/render_entity.h @@ -61,8 +61,6 @@ class RenderEntity final : public renderer::RenderEntity { /** * Get the vertices of the terrain. * - * Accessing the terrain vertices is thread-safe. - * * @return Vector of vertex coordinates. */ const std::vector get_vertices(); @@ -70,8 +68,6 @@ class RenderEntity final : public renderer::RenderEntity { /** * Get the tiles of the terrain. * - * Accessing the terrain tiles is thread-safe. - * * @return Terrain tiles. */ const tiles_t get_tiles(); @@ -79,8 +75,6 @@ class RenderEntity final : public renderer::RenderEntity { /** * Get the terrain paths used in the terrain. * - * Accessing the terrain paths is thread-safe. - * * @return Terrain paths. */ const std::unordered_set get_terrain_paths(); @@ -88,9 +82,7 @@ class RenderEntity final : public renderer::RenderEntity { /** * Get the number of vertices on each side of the terrain. * - * Accessing the vertices size is thread-safe. - * - * @return Vector with width as first element and height as second element. + * @return Number of vertices on each side (width x height). */ const util::Vector2s get_size(); diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp index 32b645a432..f4a1b239da 100644 --- a/libopenage/renderer/stages/world/object.cpp +++ b/libopenage/renderer/stages/world/object.cpp @@ -61,12 +61,16 @@ void WorldObject::fetch_updates(const time::time_t &time) { return; } - // Get data from render entity - this->ref_id = this->render_entity->get_id(); - // Thread-safe access to curves needs a lock on the render entity's mutex auto read_lock = this->render_entity->get_read_lock(); - this->position.sync(this->render_entity->get_position(), this->last_update); + + // Data syncs need to be done starting from the time of the last + // recorded change. + auto sync_time = this->render_entity->get_fetch_time(); + + // Get data from render entity + this->ref_id = this->render_entity->get_id(); + this->position.sync(this->render_entity->get_position(), sync_time); this->animation_info.sync(this->render_entity->get_animation_path(), std::function(const std::string &)>( [&](const std::string &path) { @@ -79,17 +83,16 @@ void WorldObject::fetch_updates(const time::time_t &time) { } return this->asset_manager->request_animation(path); }), - this->last_update, + sync_time, true); - this->angle.sync(this->render_entity->get_angle(), this->last_update); - - // Unlock mutex of the render entity - read_lock.unlock(); + this->angle.sync(this->render_entity->get_angle(), sync_time); // Set self to changed so that world renderer can update the renderable this->changed = true; - this->render_entity->clear_changed_flag(); this->last_update = time; + + // Indicate to the render entity that its updates have been processed. + this->render_entity->fetch_done(); } void WorldObject::update_uniforms(const time::time_t &time) { diff --git a/libopenage/renderer/stages/world/render_entity.cpp b/libopenage/renderer/stages/world/render_entity.cpp index 7a8e145741..b3f919cba3 100644 --- a/libopenage/renderer/stages/world/render_entity.cpp +++ b/libopenage/renderer/stages/world/render_entity.cpp @@ -25,6 +25,9 @@ void RenderEntity::update(const uint32_t ref_id, const time::time_t time) { std::unique_lock lock{this->mutex}; + // Sync the data curves using the earliest time of last update and time + auto sync_time = std::min(this->last_update, time); + this->ref_id = ref_id; std::function to_scene3 = [](const coord::phys3 &pos) { return pos.to_scene3(); @@ -33,11 +36,17 @@ void RenderEntity::update(const uint32_t ref_id, std::function([](const coord::phys3 &pos) { return pos.to_scene3(); }), - this->last_update); - this->angle.sync(angle, this->last_update); + sync_time); + this->angle.sync(angle, sync_time); this->animation_path.set_last(time, animation_path); - this->changed = true; + + // Record time of last update this->last_update = time; + + // Record when the render update should fetch data from the entity + this->fetch_time = std::min(this->fetch_time, time); + + this->changed = true; } void RenderEntity::update(const uint32_t ref_id, @@ -49,31 +58,26 @@ void RenderEntity::update(const uint32_t ref_id, this->ref_id = ref_id; this->position.set_last(time, position.to_scene3()); this->animation_path.set_last(time, animation_path); - this->changed = true; + this->last_update = time; + this->fetch_time = std::min(this->fetch_time, time); + + this->changed = true; } uint32_t RenderEntity::get_id() { - std::shared_lock lock{this->mutex}; - return this->ref_id; } const curve::Continuous &RenderEntity::get_position() { - std::shared_lock lock{this->mutex}; - return this->position; } const curve::Segmented &RenderEntity::get_angle() { - std::shared_lock lock{this->mutex}; - return this->angle; } const curve::Discrete &RenderEntity::get_animation_path() { - std::shared_lock lock{this->mutex}; - return this->animation_path; } diff --git a/libopenage/renderer/stages/world/render_entity.h b/libopenage/renderer/stages/world/render_entity.h index ed9011b8a4..bb467e7bc2 100644 --- a/libopenage/renderer/stages/world/render_entity.h +++ b/libopenage/renderer/stages/world/render_entity.h @@ -60,8 +60,6 @@ class RenderEntity final : public renderer::RenderEntity { /** * Get the ID of the corresponding game entity. * - * Accessing the game entity ID is thread-safe. - * * @return Game entity ID. */ uint32_t get_id(); @@ -69,9 +67,6 @@ class RenderEntity final : public renderer::RenderEntity { /** * Get the position of the entity inside the game world. * - * Accessing the position curve REQUIRES a read lock on the render entity - * (using \p get_read_lock()) to ensure thread safety. - * * @return Position curve of the entity. */ const curve::Continuous &get_position(); @@ -79,9 +74,6 @@ class RenderEntity final : public renderer::RenderEntity { /** * Get the angle of the entity inside the game world. * - * Accessing the angle curve REQUIRES a read lock on the render entity - * (using \p get_read_lock()) to ensure thread safety. - * * @return Angle curve of the entity. */ const curve::Segmented &get_angle(); @@ -89,9 +81,6 @@ class RenderEntity final : public renderer::RenderEntity { /** * Get the animation definition path. * - * Accessing the animation path curve requires a read lock on the render entity - * (using \p get_read_lock()) to ensure thread safety. - * * @return Path to the animation definition file. */ const curve::Discrete &get_animation_path(); From ae6b8c311a9cf0e15e860c6f73149b565dc21b07 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 17:12:10 +0200 Subject: [PATCH 34/42] curve: Fix compilation for oider clang versions. --- libopenage/curve/interpolated.h | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/libopenage/curve/interpolated.h b/libopenage/curve/interpolated.h index c472403963..2521c708ea 100644 --- a/libopenage/curve/interpolated.h +++ b/libopenage/curve/interpolated.h @@ -33,14 +33,25 @@ class Interpolated : public BaseCurve { * example for a <= t <= b: * val([a:x, b:y], t) = x + (y - x)/(b - a) * (t - a) */ - T get(const time::time_t &) const override; void compress(const time::time_t &start = time::TIME_MIN) override; private: - T interpolate(KeyframeContainer::elem_ptr before, - KeyframeContainer::elem_ptr after, + /** + * Get an interpolated value between two keyframes. + * + * 'before' and 'after' must be ordered such that the index of 'before' is + * less than the index of 'after'. + * + * @param before Index of the earlier keyframe. + * @param after Index of the later keyframe. + * @param elapsed Elapsed time after the earlier keyframe. + * + * @return Interpolated value. + */ + T interpolate(typename KeyframeContainer::elem_ptr before, + typename KeyframeContainer::elem_ptr after, double elapsed) const; }; @@ -119,9 +130,13 @@ void Interpolated::compress(const time::time_t &start) { } template -inline T Interpolated::interpolate(KeyframeContainer::elem_ptr before, - KeyframeContainer::elem_ptr after, +inline T Interpolated::interpolate(typename KeyframeContainer::elem_ptr before, + typename KeyframeContainer::elem_ptr after, double elapsed) const { + ENSURE(before <= after, "Index of 'before' must be before 'after'"); + ENSURE(elapsed <= (this->container.get(after).time().to_double() + - this->container.get(before).time().to_double()), + "Elapsed time must be less than or equal to the time between before and after"); // TODO: after->value - before->value will produce wrong results if // after->value < before->value and curve element type is unsigned // Example: after = 2, before = 4; type = uint8_t ==> 2 - 4 = 254 From ce7242dd57ee7253db44a75d7e0d1ec747e2ffe7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 19:51:42 +0200 Subject: [PATCH 35/42] curve: Concept for curve values. --- libopenage/curve/CMakeLists.txt | 1 + libopenage/curve/base_curve.h | 27 ++++++++++--------- libopenage/curve/concept.cpp | 9 +++++++ libopenage/curve/concept.h | 15 +++++++++++ libopenage/curve/continuous.h | 9 ++++--- libopenage/curve/discrete.h | 18 +++++-------- libopenage/curve/discrete_mod.h | 22 +++++++-------- libopenage/curve/interpolated.h | 9 ++++--- libopenage/curve/iterator.h | 3 ++- libopenage/curve/keyframe.h | 3 ++- libopenage/curve/keyframe_container.h | 33 ++++++++++++----------- libopenage/curve/map.h | 29 ++++++++++---------- libopenage/curve/map_filter_iterator.h | 7 ++--- libopenage/curve/queue.h | 25 ++++++++--------- libopenage/curve/queue_filter_iterator.h | 3 ++- libopenage/curve/segmented.h | 9 ++++--- libopenage/gamestate/component/api/live.h | 2 +- 17 files changed, 126 insertions(+), 98 deletions(-) create mode 100644 libopenage/curve/concept.cpp create mode 100644 libopenage/curve/concept.h diff --git a/libopenage/curve/CMakeLists.txt b/libopenage/curve/CMakeLists.txt index 1aa1efb55b..60680c1dac 100644 --- a/libopenage/curve/CMakeLists.txt +++ b/libopenage/curve/CMakeLists.txt @@ -1,5 +1,6 @@ add_sources(libopenage base_curve.cpp + concept.cpp continuous.cpp discrete.cpp discrete_mod.cpp diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index fc82d1bb82..123a67c88d 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -14,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" @@ -27,7 +28,7 @@ class EventLoop; namespace curve { -template +template class BaseCurve : public event::EventEntity { public: BaseCurve(const std::shared_ptr &loop, @@ -170,7 +171,7 @@ class BaseCurve : public event::EventEntity { * Redundant keyframes are keyframes that don't change the value * calculaton of the curve at any given time, e.g. duplicate keyframes. */ - template + template void sync(const BaseCurve &other, const std::function &converter, const time::time_t &start = time::TIME_MIN, @@ -239,7 +240,7 @@ class BaseCurve : public event::EventEntity { }; -template +template void BaseCurve::set_last(const time::time_t &at, const T &value, bool compress) { @@ -266,7 +267,7 @@ void BaseCurve::set_last(const time::time_t &at, } -template +template void BaseCurve::set_insert(const time::time_t &at, const T &value, bool compress) { @@ -286,7 +287,7 @@ void BaseCurve::set_insert(const time::time_t &at, } -template +template void BaseCurve::set_replace(const time::time_t &at, const T &value) { this->container.insert_overwrite(at, value, this->last_element); @@ -294,14 +295,14 @@ void BaseCurve::set_replace(const time::time_t &at, } -template +template void BaseCurve::erase(const time::time_t &at) { this->last_element = this->container.erase(at, this->last_element); this->changes(at); } -template +template std::pair BaseCurve::frame(const time::time_t &time) const { auto e = this->container.last(time, this->container.size()); auto elem = this->container.get(e); @@ -309,7 +310,7 @@ std::pair BaseCurve::frame(const time::time_t &time) c } -template +template std::pair BaseCurve::next_frame(const time::time_t &time) const { auto e = this->container.last(time, this->container.size()); e++; @@ -317,7 +318,7 @@ std::pair BaseCurve::next_frame(const time::time_t &ti return std::make_pair(elem.time(), elem.val()); } -template +template std::string BaseCurve::str() const { std::stringstream ss; ss << "Curve[" << this->idstr() << "]{" << std::endl; @@ -329,7 +330,7 @@ std::string BaseCurve::str() const { return ss.str(); } -template +template void BaseCurve::check_integrity() const { time::time_t last_time = time::TIME_MIN; for (const auto &keyframe : this->container) { @@ -340,7 +341,7 @@ void BaseCurve::check_integrity() const { } } -template +template void BaseCurve::sync(const BaseCurve &other, const time::time_t &start, bool compress) { @@ -362,8 +363,8 @@ void BaseCurve::sync(const BaseCurve &other, } -template -template +template +template void BaseCurve::sync(const BaseCurve &other, const std::function &converter, const time::time_t &start, diff --git a/libopenage/curve/concept.cpp b/libopenage/curve/concept.cpp new file mode 100644 index 0000000000..aa1b8c4612 --- /dev/null +++ b/libopenage/curve/concept.cpp @@ -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 diff --git a/libopenage/curve/concept.h b/libopenage/curve/concept.h new file mode 100644 index 0000000000..8d05df948a --- /dev/null +++ b/libopenage/curve/concept.h @@ -0,0 +1,15 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +namespace openage::curve { + +/** + * Concept for keyframe values. + */ +template +concept KeyframeValueLike = std::copyable && std::equality_comparable; + +} // namespace openage::curve diff --git a/libopenage/curve/continuous.h b/libopenage/curve/continuous.h index 92f06053d8..9f61bb166c 100644 --- a/libopenage/curve/continuous.h +++ b/libopenage/curve/continuous.h @@ -5,6 +5,7 @@ #include #include +#include "curve/concept.h" #include "curve/interpolated.h" #include "time/time.h" @@ -22,7 +23,7 @@ namespace openage::curve { * The bound template type T has to implement `operator+(T)` and * `operator*(time::time_t)`. */ -template +template class Continuous : public Interpolated { public: using Interpolated::Interpolated; @@ -47,7 +48,7 @@ class Continuous : public Interpolated { }; -template +template void Continuous::set_last(const time::time_t &at, const T &value, bool compress) { @@ -74,7 +75,7 @@ void Continuous::set_last(const time::time_t &at, } -template +template void Continuous::set_insert(const time::time_t &t, const T &value, bool /* compress */) { @@ -82,7 +83,7 @@ void Continuous::set_insert(const time::time_t &t, } -template +template std::string Continuous::idstr() const { std::stringstream ss; ss << "ContinuousCurve["; diff --git a/libopenage/curve/discrete.h b/libopenage/curve/discrete.h index 153e0a7735..a7af01f1b6 100644 --- a/libopenage/curve/discrete.h +++ b/libopenage/curve/discrete.h @@ -9,6 +9,7 @@ #include #include "curve/base_curve.h" +#include "curve/concept.h" #include "time/time.h" @@ -18,13 +19,8 @@ namespace openage::curve { * Does not interpolate between values. The template type does only need to * implement `operator=` and copy ctor. */ -template +template class Discrete : public BaseCurve { - static_assert(std::is_copy_assignable::value, - "Template type is not copy assignable"); - static_assert(std::is_copy_constructible::value, - "Template type is not copy constructible"); - public: using BaseCurve::BaseCurve; @@ -53,14 +49,14 @@ class Discrete : public BaseCurve { }; -template +template 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 this->container.get(e).val(); } -template +template void Discrete::compress(const time::time_t &start) { auto e = this->container.last_before(start, this->last_element); @@ -88,7 +84,7 @@ void Discrete::compress(const time::time_t &start) { this->changes(start); } -template +template std::string Discrete::idstr() const { std::stringstream ss; ss << "DiscreteCurve["; @@ -103,7 +99,7 @@ std::string Discrete::idstr() const { } -template +template std::pair Discrete::get_time(const time::time_t &time) const { auto e = this->container.last(time, this->last_element); this->last_element = e; @@ -113,7 +109,7 @@ std::pair Discrete::get_time(const time::time_t &time) const } -template +template 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 4ff51437af..33adcbbccc 100644 --- a/libopenage/curve/discrete_mod.h +++ b/libopenage/curve/discrete_mod.h @@ -9,6 +9,7 @@ #include #include "curve/base_curve.h" +#include "curve/concept.h" #include "curve/discrete.h" #include "time/time.h" #include "util/fixed_point.h" @@ -27,13 +28,8 @@ namespace openage::curve { * always be inserted at t = 0. Also, the last keyframe should have the same value * as the first keyframe as a convention. */ -template +template class DiscreteMod : public Discrete { - static_assert(std::is_copy_assignable::value, - "Template type is not copy assignable"); - static_assert(std::is_copy_constructible::value, - "Template type is not copy constructible"); - public: using Discrete::Discrete; @@ -75,7 +71,7 @@ class DiscreteMod : public Discrete { }; -template +template void DiscreteMod::set_last(const time::time_t &at, const T &value, bool compress) { @@ -84,7 +80,7 @@ void DiscreteMod::set_last(const time::time_t &at, } -template +template void DiscreteMod::set_insert(const time::time_t &at, const T &value, bool compress) { @@ -96,7 +92,7 @@ void DiscreteMod::set_insert(const time::time_t &at, } -template +template void DiscreteMod::erase(const time::time_t &at) { BaseCurve::erase(at); @@ -106,7 +102,7 @@ void DiscreteMod::erase(const time::time_t &at) { } -template +template std::string DiscreteMod::idstr() const { std::stringstream ss; ss << "DiscreteRingCurve["; @@ -121,7 +117,7 @@ std::string DiscreteMod::idstr() const { } -template +template 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) { @@ -134,7 +130,7 @@ T DiscreteMod::get_mod(const time::time_t &time, const time::time_t &start) c } -template +template std::pair DiscreteMod::get_time_mod(const time::time_t &time, const time::time_t &start) const { time::time_t offset = time - start; @@ -148,7 +144,7 @@ std::pair DiscreteMod::get_time_mod(const time::time_t &time } -template +template std::optional> DiscreteMod::get_previous_mod(const time::time_t &time, const time::time_t &start) const { time::time_t offset = time - start; diff --git a/libopenage/curve/interpolated.h b/libopenage/curve/interpolated.h index 2521c708ea..89a0d6b2c9 100644 --- a/libopenage/curve/interpolated.h +++ b/libopenage/curve/interpolated.h @@ -3,6 +3,7 @@ #pragma once #include "curve/base_curve.h" +#include "curve/concept.h" #include "time/time.h" #include "util/fixed_point.h" @@ -17,7 +18,7 @@ namespace openage::curve { * The bound template type T has to implement `operator +(T)` and * `operator *(time::time_t)`. */ -template +template class Interpolated : public BaseCurve { public: using BaseCurve::BaseCurve; @@ -56,7 +57,7 @@ class Interpolated : public BaseCurve { }; -template +template T Interpolated::get(const time::time_t &time) const { const auto e = this->container.last(time, this->last_element); this->last_element = e; @@ -91,7 +92,7 @@ T Interpolated::get(const time::time_t &time) const { } } -template +template void Interpolated::compress(const time::time_t &start) { // Find the last element before the start time auto e = this->container.last_before(start, this->last_element); @@ -129,7 +130,7 @@ void Interpolated::compress(const time::time_t &start) { this->changes(start); } -template +template inline T Interpolated::interpolate(typename KeyframeContainer::elem_ptr before, typename KeyframeContainer::elem_ptr after, double elapsed) const { diff --git a/libopenage/curve/iterator.h b/libopenage/curve/iterator.h index d0346e1b5d..dd7c5f29eb 100644 --- a/libopenage/curve/iterator.h +++ b/libopenage/curve/iterator.h @@ -2,6 +2,7 @@ #pragma once +#include "curve/concept.h" #include "time/time.h" #include "util/fixed_point.h" @@ -11,7 +12,7 @@ namespace openage::curve { /** * Default interface for curve containers */ -template class CurveIterator { diff --git a/libopenage/curve/keyframe.h b/libopenage/curve/keyframe.h index e868783cbb..c8178ed339 100644 --- a/libopenage/curve/keyframe.h +++ b/libopenage/curve/keyframe.h @@ -2,6 +2,7 @@ #pragma once +#include "curve/concept.h" #include "time/time.h" #include "util/fixed_point.h" @@ -15,7 +16,7 @@ namespace openage::curve { * If you change this class, remember to update the gdb pretty printers * in etc/gdb_pretty/printers.py. */ -template +template class Keyframe { public: /** diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index e8987aab4b..a69a2c9616 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -7,6 +7,7 @@ #include #include +#include "curve/concept.h" #include "curve/keyframe.h" #include "time/time.h" #include "util/fixed_point.h" @@ -23,7 +24,7 @@ namespace openage::curve { * the exact timestamp has to be known, it will always return the one closest, * less or equal to the requested one. **/ -template +template class KeyframeContainer { public: /** @@ -379,7 +380,7 @@ class KeyframeContainer { * Using the default value replaces ALL keyframes of \p this with * the keyframes of \p other. */ - template + template elem_ptr sync(const KeyframeContainer &other, const std::function &converter, const time::time_t &start = time::TIME_MIN); @@ -408,7 +409,7 @@ class KeyframeContainer { }; -template +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 @@ -416,7 +417,7 @@ KeyframeContainer::KeyframeContainer() { } -template +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 @@ -424,7 +425,7 @@ KeyframeContainer::KeyframeContainer(const T &defaultval) { } -template +template size_t KeyframeContainer::size() const { return this->container.size(); } @@ -438,7 +439,7 @@ size_t KeyframeContainer::size() const { * Intuitively, this function returns the element that set the last value * that determines the curve value for a searched time. */ -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::last(const time::time_t &time, const KeyframeContainer::elem_ptr &hint) const { @@ -474,7 +475,7 @@ KeyframeContainer::last(const time::time_t &time, * * ASDF: Remove all comments for the implementations. */ -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::last_before(const time::time_t &time, const KeyframeContainer::elem_ptr &hint) const { @@ -503,7 +504,7 @@ KeyframeContainer::last_before(const time::time_t &time, /* * Determine where to insert based on time, and insert. */ -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::insert_before(const KeyframeContainer::keyframe_t &e, const KeyframeContainer::elem_ptr &hint) { @@ -529,7 +530,7 @@ KeyframeContainer::insert_before(const KeyframeContainer::keyframe_t &e, /* * Determine where to insert based on time, and insert, overwriting value(s) with same time. */ -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::insert_overwrite(const KeyframeContainer::keyframe_t &e, const KeyframeContainer::elem_ptr &hint, @@ -559,7 +560,7 @@ KeyframeContainer::insert_overwrite(const KeyframeContainer::keyframe_t &e * Determine where to insert based on time, and insert. * If there is a time conflict, insert after the existing element. */ -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::insert_after(const KeyframeContainer::keyframe_t &e, const KeyframeContainer::elem_ptr &hint) { @@ -578,7 +579,7 @@ KeyframeContainer::insert_after(const KeyframeContainer::keyframe_t &e, /* * Go from the end to the last_valid element, and call erase on all of them */ -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::erase_after(KeyframeContainer::elem_ptr last_valid) { // exclude the last_valid element from deletion @@ -595,7 +596,7 @@ KeyframeContainer::erase_after(KeyframeContainer::elem_ptr last_valid) { /* * Delete the element from the list and call delete on it. */ -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::erase(KeyframeContainer::elem_ptr e) { this->container.erase(this->begin() + e); @@ -603,7 +604,7 @@ KeyframeContainer::erase(KeyframeContainer::elem_ptr e) { } -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::sync(const KeyframeContainer &other, const time::time_t &start) { @@ -624,8 +625,8 @@ KeyframeContainer::sync(const KeyframeContainer &other, } -template -template +template +template typename KeyframeContainer::elem_ptr KeyframeContainer::sync(const KeyframeContainer &other, const std::function &converter, @@ -648,7 +649,7 @@ KeyframeContainer::sync(const KeyframeContainer &other, } -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::erase_group(const time::time_t &time, const KeyframeContainer::elem_ptr &last_elem) { diff --git a/libopenage/curve/map.h b/libopenage/curve/map.h index 9712c89470..49724cad69 100644 --- a/libopenage/curve/map.h +++ b/libopenage/curve/map.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -7,6 +7,7 @@ #include #include +#include "curve/concept.h" #include "curve/map_filter_iterator.h" #include "time/time.h" #include "util/fixed_point.h" @@ -18,7 +19,7 @@ namespace openage::curve { * Map that keeps track of the lifetime of the contained elements. * Make sure that no key is reused. */ -template +template class UnorderedMap { /** Internal container to access all data and metadata */ struct map_element { @@ -83,14 +84,14 @@ class UnorderedMap { } }; -template +template std::optional>> UnorderedMap::operator()(const time::time_t &time, const key_t &key) const { return this->at(time, key); } -template +template std::optional>> UnorderedMap::at(const time::time_t &time, const key_t &key) const { @@ -107,7 +108,7 @@ UnorderedMap::at(const time::time_t &time, } } -template +template MapFilterIterator> UnorderedMap::begin(const time::time_t &time) const { return MapFilterIterator>( @@ -117,7 +118,7 @@ UnorderedMap::begin(const time::time_t &time) const { time::TIME_MAX); } -template +template MapFilterIterator> UnorderedMap::end(const time::time_t &time) const { return MapFilterIterator>( @@ -127,7 +128,7 @@ UnorderedMap::end(const time::time_t &time) const { time); } -template +template MapFilterIterator> UnorderedMap::between(const time::time_t &from, const time::time_t &to) const { auto it = MapFilterIterator>( @@ -142,7 +143,7 @@ UnorderedMap::between(const time::time_t &from, const time::time_t return it; } -template +template MapFilterIterator> UnorderedMap::insert(const time::time_t &alive, const key_t &key, @@ -154,7 +155,7 @@ UnorderedMap::insert(const time::time_t &alive, value); } -template +template MapFilterIterator> UnorderedMap::insert(const time::time_t &alive, const time::time_t &dead, @@ -169,7 +170,7 @@ UnorderedMap::insert(const time::time_t &alive, dead); } -template +template void UnorderedMap::birth(const time::time_t &time, const key_t &key) { auto it = this->container.find(key); @@ -178,13 +179,13 @@ void UnorderedMap::birth(const time::time_t &time, } } -template +template void UnorderedMap::birth(const time::time_t &time, const MapFilterIterator &it) { it->second.alive = time; } -template +template void UnorderedMap::kill(const time::time_t &time, const key_t &key) { auto it = this->container.find(key); @@ -193,13 +194,13 @@ void UnorderedMap::kill(const time::time_t &time, } } -template +template void UnorderedMap::kill(const time::time_t &time, const MapFilterIterator &it) { it->second.dead = time; } -template +template void UnorderedMap::clean(const time::time_t &) { // TODO save everything to a file and be happy. } diff --git a/libopenage/curve/map_filter_iterator.h b/libopenage/curve/map_filter_iterator.h index 5e71c0789f..0e2e98f813 100644 --- a/libopenage/curve/map_filter_iterator.h +++ b/libopenage/curve/map_filter_iterator.h @@ -1,7 +1,8 @@ -// Copyright 2017-2023 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 "curve/iterator.h" #include "time/time.h" @@ -16,8 +17,8 @@ namespace openage::curve { * It depends on key_t and val_t as map-parameters, container_t is the container * to operate on and the function valid_f, that checks if an element is alive. */ -template class MapFilterIterator : public CurveIterator { public: diff --git a/libopenage/curve/queue.h b/libopenage/curve/queue.h index 9604a76308..9e652b92e1 100644 --- a/libopenage/curve/queue.h +++ b/libopenage/curve/queue.h @@ -11,6 +11,7 @@ #include "error/error.h" +#include "curve/concept.h" #include "curve/iterator.h" #include "curve/queue_filter_iterator.h" #include "event/evententity.h" @@ -30,7 +31,7 @@ namespace curve { * time it will happen. * This container can be used to store interactions */ -template +template class Queue : public event::EventEntity { struct queue_wrapper { // Insertion time of the element @@ -269,7 +270,7 @@ class Queue : public event::EventEntity { }; -template +template typename Queue::elem_ptr Queue::first_alive(const time::time_t &time) const { elem_ptr hint = 0; @@ -293,7 +294,7 @@ typename Queue::elem_ptr Queue::first_alive(const time::time_t &time) cons } -template +template const T &Queue::front(const time::time_t &time) const { elem_ptr at = this->first_alive(time); ENSURE(at < this->container.size(), @@ -308,7 +309,7 @@ const T &Queue::front(const time::time_t &time) const { } -template +template const T &Queue::pop_front(const time::time_t &time) { elem_ptr at = this->first_alive(time); ENSURE(at < this->container.size(), @@ -334,7 +335,7 @@ const T &Queue::pop_front(const time::time_t &time) { } -template +template bool Queue::empty(const time::time_t &time) const { if (this->container.empty()) { return true; @@ -344,7 +345,7 @@ bool Queue::empty(const time::time_t &time) const { } -template +template QueueFilterIterator> Queue::begin(const time::time_t &t) const { for (auto it = this->container.begin(); it != this->container.end(); ++it) { if (it->alive() >= t) { @@ -360,7 +361,7 @@ QueueFilterIterator> Queue::begin(const time::time_t &t) const { } -template +template QueueFilterIterator> Queue::end(const time::time_t &t) const { return QueueFilterIterator>( container.end(), @@ -370,7 +371,7 @@ QueueFilterIterator> Queue::end(const time::time_t &t) const { } -template +template QueueFilterIterator> Queue::between(const time::time_t &begin, const time::time_t &end) const { auto it = QueueFilterIterator>( @@ -385,20 +386,20 @@ QueueFilterIterator> Queue::between(const time::time_t &begin, } -template +template void Queue::erase(const CurveIterator> &it) { container.erase(it.get_base()); } -template +template void Queue::kill(const time::time_t &time, elem_ptr at) { this->container[at].set_dead(time); } -template +template QueueFilterIterator> Queue::insert(const time::time_t &time, const T &e) { elem_ptr at = this->container.size(); @@ -442,7 +443,7 @@ QueueFilterIterator> Queue::insert(const time::time_t &time, } -template +template void Queue::clear(const time::time_t &time) { elem_ptr at = this->first_alive(time); diff --git a/libopenage/curve/queue_filter_iterator.h b/libopenage/curve/queue_filter_iterator.h index 248abb44ad..36dff630a7 100644 --- a/libopenage/curve/queue_filter_iterator.h +++ b/libopenage/curve/queue_filter_iterator.h @@ -2,6 +2,7 @@ #pragma once +#include "curve/concept.h" #include "curve/iterator.h" #include "time/time.h" @@ -16,7 +17,7 @@ namespace openage::curve { * It depends on val_t as its value type, container_t is the container * to operate on and the function valid_f, that checks if an element is alive. */ -template class QueueFilterIterator : public CurveIterator { public: diff --git a/libopenage/curve/segmented.h b/libopenage/curve/segmented.h index 98add2995e..2d1ced622b 100644 --- a/libopenage/curve/segmented.h +++ b/libopenage/curve/segmented.h @@ -5,6 +5,7 @@ #include #include +#include "curve/concept.h" #include "curve/interpolated.h" #include "time/time.h" @@ -25,7 +26,7 @@ namespace openage::curve { * The bound template type T has to implement `operator +(T)` and * `operator *(time::time_t)`. */ -template +template class Segmented : public Interpolated { public: using Interpolated::Interpolated; @@ -49,7 +50,7 @@ class Segmented : public Interpolated { }; -template +template void Segmented::set_insert_jump(const time::time_t &at, const T &leftval, const T &rightval) { auto hint = this->container.insert_overwrite(at, leftval, this->last_element, true); this->container.insert_after(at, rightval, hint); @@ -57,7 +58,7 @@ void Segmented::set_insert_jump(const time::time_t &at, const T &leftval, con } -template +template void Segmented::set_last_jump(const time::time_t &at, const T &leftval, const T &rightval) { auto hint = this->container.last(at, this->last_element); @@ -76,7 +77,7 @@ void Segmented::set_last_jump(const time::time_t &at, const T &leftval, const } -template +template std::string Segmented::idstr() const { std::stringstream ss; ss << "SegmentedCurve["; diff --git a/libopenage/gamestate/component/api/live.h b/libopenage/gamestate/component/api/live.h index 02f347e219..e8638b7a39 100644 --- a/libopenage/gamestate/component/api/live.h +++ b/libopenage/gamestate/component/api/live.h @@ -16,7 +16,7 @@ namespace openage { namespace curve { -template +template class Segmented; } // namespace curve From 87e7ebef6b1dad0121f810899f5e1ab0af39019c Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 22 Oct 2024 06:06:49 +0200 Subject: [PATCH 36/42] doc: Add documentation for curve compression. --- doc/code/curves.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/doc/code/curves.md b/doc/code/curves.md index 376c4c938c..1a4c7f6b4a 100644 --- a/doc/code/curves.md +++ b/doc/code/curves.md @@ -17,6 +17,7 @@ Curves are an integral part of openage's event-based game simulation. 2. [Container](#container) 1. [Queue](#queue) 2. [Unordered Map](#unordered-map) +4. [Compression](#compression) ## Motivation @@ -132,6 +133,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** @@ -253,3 +257,27 @@ 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`. + +## 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)`. From 444428465cd2662029bff6332c48d3d5a4119a99 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 4 Nov 2024 02:00:19 +0100 Subject: [PATCH 37/42] gamestate: Add activity node type for branching on value. --- doc/code/game_simulation/activity.md | 17 +- libopenage/gamestate/activity/CMakeLists.txt | 1 + libopenage/gamestate/activity/types.h | 1 + .../gamestate/activity/xor_event_gate.h | 2 +- libopenage/gamestate/activity/xor_gate.h | 2 +- .../gamestate/activity/xor_switch_gate.cpp | 53 ++++++ .../gamestate/activity/xor_switch_gate.h | 152 ++++++++++++++++++ 7 files changed, 218 insertions(+), 10 deletions(-) create mode 100644 libopenage/gamestate/activity/xor_switch_gate.cpp create mode 100644 libopenage/gamestate/activity/xor_switch_gate.h diff --git a/doc/code/game_simulation/activity.md b/doc/code/game_simulation/activity.md index 0b84864fe9..edd623f419 100644 --- a/doc/code/game_simulation/activity.md +++ b/doc/code/game_simulation/activity.md @@ -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 | diff --git a/libopenage/gamestate/activity/CMakeLists.txt b/libopenage/gamestate/activity/CMakeLists.txt index 78a78e7ab0..fe0f5989eb 100644 --- a/libopenage/gamestate/activity/CMakeLists.txt +++ b/libopenage/gamestate/activity/CMakeLists.txt @@ -9,6 +9,7 @@ add_sources(libopenage types.cpp xor_event_gate.cpp xor_gate.cpp + xor_switch_gate.cpp ) add_subdirectory("event") diff --git a/libopenage/gamestate/activity/types.h b/libopenage/gamestate/activity/types.h index ac2189e5ab..1b15ca0f67 100644 --- a/libopenage/gamestate/activity/types.h +++ b/libopenage/gamestate/activity/types.h @@ -13,6 +13,7 @@ enum class node_t { END, XOR_EVENT_GATE, XOR_GATE, + XOR_SWITCH_GATE, TASK_CUSTOM, TASK_SYSTEM, }; diff --git a/libopenage/gamestate/activity/xor_event_gate.h b/libopenage/gamestate/activity/xor_event_gate.h index 38c8ccacb1..64e195faf3 100644 --- a/libopenage/gamestate/activity/xor_event_gate.h +++ b/libopenage/gamestate/activity/xor_event_gate.h @@ -61,7 +61,7 @@ class XorEventGate : public Node { * @param label Human-readable label (optional). */ XorEventGate(node_id_t id, - node_label_t label = "EventGateWay"); + node_label_t label = "ExclusiveEventGateway"); /** * Create a new exclusive event gateway. diff --git a/libopenage/gamestate/activity/xor_gate.h b/libopenage/gamestate/activity/xor_gate.h index f73287ed86..0ced1d6d27 100644 --- a/libopenage/gamestate/activity/xor_gate.h +++ b/libopenage/gamestate/activity/xor_gate.h @@ -24,7 +24,7 @@ namespace activity { /** * Function that determines if an output node is chosen. * - * @param time Current game time. + * @param time Current simulation time. * @param entity Entity that is executing the activity. * * @return true if the output node is chosen, false otherwise. diff --git a/libopenage/gamestate/activity/xor_switch_gate.cpp b/libopenage/gamestate/activity/xor_switch_gate.cpp new file mode 100644 index 0000000000..1ae1445e09 --- /dev/null +++ b/libopenage/gamestate/activity/xor_switch_gate.cpp @@ -0,0 +1,53 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "xor_switch_gate.h" + + +namespace openage::gamestate::activity { + +XorSwitchGate::XorSwitchGate(node_id_t id, + node_label_t label) : + Node{id, label} {} + +XorSwitchGate::XorSwitchGate(node_id_t id, + node_label_t label, + const lookup_function_t &lookup_func, + const lookup_dict_t &lookup_dict, + const std::shared_ptr &default_node) : + Node{id, label}, + lookup_func{lookup_func}, + lookup_dict{lookup_dict}, + default_node{default_node} {} + +void XorSwitchGate::set_output(const std::shared_ptr &output, + const lookup_key_t &key) { + this->outputs.emplace(output->get_id(), output); + this->lookup_dict.emplace(key, output); +} + +const XorSwitchGate::lookup_function_t &XorSwitchGate::get_lookup_func() const { + return this->lookup_func; +} + +void XorSwitchGate::set_lookup_func(const lookup_function_t &lookup_func) { + this->lookup_func = lookup_func; +} + +const XorSwitchGate::lookup_dict_t &XorSwitchGate::get_lookup_dict() const { + return this->lookup_dict; +} + +const std::shared_ptr &XorSwitchGate::get_default() const { + return this->default_node; +} + +void XorSwitchGate::set_default(const std::shared_ptr &node) { + if (this->default_node != nullptr) { + throw Error{MSG(err) << this->str() << " already has a default node"}; + } + + this->outputs.emplace(node->get_id(), node); + this->default_node = node; +} + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/xor_switch_gate.h b/libopenage/gamestate/activity/xor_switch_gate.h new file mode 100644 index 0000000000..371c11651e --- /dev/null +++ b/libopenage/gamestate/activity/xor_switch_gate.h @@ -0,0 +1,152 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "gamestate/activity/node.h" +#include "gamestate/activity/types.h" +#include "time/time.h" + + +namespace openage::gamestate { +class GameEntity; + +namespace activity { + +/** + * Chooses one of its output nodes based on enum values. + * + * In comparison to the XOR gate, this node type does not tie individual + * conditions to each node. Instead, it operates on a single function that + * returns a key for a lookup dict. The lookup dict maps these keys to output + * node ID. + * + * This type of gate is easier to use for simpler branch switches based on + * similar conditions, e.g. a branching based on the value of an enum. + */ +class XorSwitchGate : public Node { +public: + /** + * Type used as lookup key for the lookup dict. + */ + using lookup_key_t = int; + + /** + * Function that retrieves a lookup key for the lookup dict from + * the current state of an entity. + * + * @param time Current simulation time. + * @param entity Entity that is executing the activity. + * + * @return Lookup key. + */ + using lookup_function_t = std::function &)>; + + /** + * Lookup dict that maps lookup keys to output node IDs. + */ + using lookup_dict_t = std::unordered_map>; + + /** + * Creates a new XOR switch gate node. + * + * @param id Unique identifier of the node. + * @param label Human-readable label of the node (optional). + */ + XorSwitchGate(node_id_t id, + node_label_t label = "ExclusiveSwitchGateway"); + + /** + * Creates a new XOR switch gate node. + * + * @param id Unique identifier of the node. + * @param label Human-readable label of the node. + * @param lookup_func Function that looks up the key to the lookup dict. + * @param lookup_dict Initial lookup dict that maps lookup keys to output node IDs. + * @param default_node Default output node. Chosen if no lookup entry is defined. + */ + XorSwitchGate(node_id_t id, + node_label_t label, + const lookup_function_t &lookup_func, + const lookup_dict_t &lookup_dict, + const std::shared_ptr &default_node); + + virtual ~XorSwitchGate() = default; + + inline node_t get_type() const override { + return node_t::XOR_SWITCH_GATE; + } + + /** + * Set the output node for a given lookup key. + * + * @param output Output node. + * @param key Enumeration value. + */ + void set_output(const std::shared_ptr &output, + const lookup_key_t &key); + + /** + * Get the lookup function for determining the output nodes. + * + * @return Lookup function. + */ + const lookup_function_t &get_lookup_func() const; + + /** + * Set the lookup function for determining the output nodes. + * + * @param lookup_func Lookup function. + */ + void set_lookup_func(const lookup_function_t &lookup_func); + + /** + * Get the lookup dict for the output nodes. + * + * @return Lookup dict. + */ + const lookup_dict_t &get_lookup_dict() const; + + /** + * Get the default output node. + * + * @return Default output node. + */ + const std::shared_ptr &get_default() const; + + /** + * Set the the default output node. + * + * This node is chosen if the lookup dict does not contain an entry for the + * lookup key returned by the lookup function. + * + * @param node Default output node. + */ + void set_default(const std::shared_ptr &node); + +private: + /** + * Determines the lookup key for the lookup dict from the current state. + */ + lookup_function_t lookup_func; + + /** + * Maps lookup keys to output node IDs. + */ + lookup_dict_t lookup_dict; + + /** + * Default output node. Chosen if no lookup entry is defined. + */ + std::shared_ptr default_node; +}; + +} // namespace activity +} // namespace openage::gamestate From 9878f65961c54f0792989712ae18ff141f671c51 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 04:23:10 +0100 Subject: [PATCH 38/42] gamestate: Handle XorSwichGate in activity system. --- libopenage/gamestate/activity/types.h | 2 +- libopenage/gamestate/system/activity.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/libopenage/gamestate/activity/types.h b/libopenage/gamestate/activity/types.h index 1b15ca0f67..f9e630171b 100644 --- a/libopenage/gamestate/activity/types.h +++ b/libopenage/gamestate/activity/types.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index d5cb2da9d1..92819ab556 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.cpp @@ -16,6 +16,7 @@ #include "gamestate/activity/types.h" #include "gamestate/activity/xor_event_gate.h" #include "gamestate/activity/xor_gate.h" +#include "gamestate/activity/xor_switch_gate.h" #include "gamestate/component/internal/activity.h" #include "gamestate/component/types.h" #include "gamestate/game_entity.h" @@ -112,6 +113,15 @@ void Activity::advance(const time::time_t &start_time, event_wait_time = 0; stop = true; } break; + case activity::node_t::XOR_SWITCH_GATE: { + auto node = std::dynamic_pointer_cast(current_node); + auto next_id = node->get_default()->get_id(); + auto key = node->get_lookup_func()(start_time, entity); + if (node->get_lookup_dict().contains(key)) { + next_id = node->get_lookup_dict().at(key)->get_id(); + } + current_node = node->next(next_id); + } break; default: throw Error{ERR << "Unhandled node type for node " << current_node->str()}; } From a8709de3ef5796adae107b1f59305dc7a9d2f89e Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 05:11:38 +0100 Subject: [PATCH 39/42] gamestate: Add unit tests for activity node types. --- libopenage/gamestate/activity/CMakeLists.txt | 1 + .../gamestate/activity/tests/CMakeLists.txt | 3 + .../gamestate/activity/tests/node_types.cpp | 251 ++++++++++++++++++ openage/testing/testlist.py | 1 + 4 files changed, 256 insertions(+) create mode 100644 libopenage/gamestate/activity/tests/CMakeLists.txt create mode 100644 libopenage/gamestate/activity/tests/node_types.cpp diff --git a/libopenage/gamestate/activity/CMakeLists.txt b/libopenage/gamestate/activity/CMakeLists.txt index fe0f5989eb..408c88681e 100644 --- a/libopenage/gamestate/activity/CMakeLists.txt +++ b/libopenage/gamestate/activity/CMakeLists.txt @@ -14,3 +14,4 @@ add_sources(libopenage add_subdirectory("event") add_subdirectory("condition") +add_subdirectory("tests") diff --git a/libopenage/gamestate/activity/tests/CMakeLists.txt b/libopenage/gamestate/activity/tests/CMakeLists.txt new file mode 100644 index 0000000000..a4d787ae45 --- /dev/null +++ b/libopenage/gamestate/activity/tests/CMakeLists.txt @@ -0,0 +1,3 @@ +add_sources(libopenage + node_types.cpp +) diff --git a/libopenage/gamestate/activity/tests/node_types.cpp b/libopenage/gamestate/activity/tests/node_types.cpp new file mode 100644 index 0000000000..702143b251 --- /dev/null +++ b/libopenage/gamestate/activity/tests/node_types.cpp @@ -0,0 +1,251 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include + +#include "gamestate/activity/end_node.h" +#include "gamestate/activity/start_node.h" +#include "gamestate/activity/task_node.h" +#include "gamestate/activity/task_system_node.h" +#include "gamestate/activity/types.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" +#include "gamestate/activity/xor_switch_gate.h" +#include "gamestate/system/types.h" +#include "testing/testing.h" + + +namespace openage::gamestate::activity::tests { + +/** + * Unit tests for the activity node types. + */ +void node_types() { + // Start node type + { + auto start_node = std::make_shared(0); + + // Check basic properties inherited from Node interface + TESTEQUALS(start_node->get_id(), 0); + TESTEQUALS(static_cast(start_node->get_type()), static_cast(node_t::START)); + TESTEQUALS(start_node->get_label(), "Start"); + TESTEQUALS(start_node->str(), "Start (id=0)"); + + auto next_node = std::make_shared(1); + start_node->add_output(next_node); + + // Check the next node + TESTEQUALS(start_node->get_next(), 1); + TESTEQUALS(start_node->next(1), next_node); + + // Check that the node throws errors for invalid output IDs + TESTTHROWS(start_node->next(999)); + } + + // End node type + { + auto end_node = std::make_shared(0); + + // Check basic properties inherited from Node interface + TESTEQUALS(end_node->get_id(), 0); + // TESTEQUALS(end_node->get_type(), node_t::END); + TESTEQUALS(end_node->get_label(), "End"); + TESTEQUALS(end_node->str(), "End (id=0)"); + + // End nodes have no outputs + TESTTHROWS(end_node->next(999)); + } + + // Task system node type + { + auto task_system_node = std::make_shared(0); + + // Check basic properties inherited from Node interface + TESTEQUALS(task_system_node->get_id(), 0); + // TESTEQUALS(task_system_node->get_type(), node_t::TASK_SYSTEM); + TESTEQUALS(task_system_node->get_label(), "TaskSystem"); + TESTEQUALS(task_system_node->str(), "TaskSystem (id=0)"); + + auto next_node = std::make_shared(1); + task_system_node->add_output(next_node); + + // Check the next node + TESTEQUALS(task_system_node->get_next(), 1); + TESTEQUALS(task_system_node->next(1), next_node); + + // Check that the node throws errors for invalid output IDs + TESTTHROWS(task_system_node->next(999)); + + auto sytem_id = system::system_id_t::MOVE_DEFAULT; + task_system_node->set_system_id(sytem_id); + + // Check the system ID + TESTEQUALS(static_cast(task_system_node->get_system_id()), static_cast(sytem_id)); + } + + // Task custom node type + { + auto task_custom_node = std::make_shared(0); + + // Check basic properties inherited from Node interface + TESTEQUALS(task_custom_node->get_id(), 0); + // TESTEQUALS(task_custom_node->get_type(), node_t::TASK_CUSTOM); + TESTEQUALS(task_custom_node->get_label(), "TaskCustom"); + TESTEQUALS(task_custom_node->str(), "TaskCustom (id=0)"); + + auto next_node = std::make_shared(1); + task_custom_node->add_output(next_node); + + // Check the next node + TESTEQUALS(task_custom_node->get_next(), 1); + TESTEQUALS(task_custom_node->next(1), next_node); + + // Check that the node throws errors for invalid output IDs + TESTTHROWS(task_custom_node->next(999)); + + auto task_func = [](const time::time_t & /* time */, const std::shared_ptr & /* entity */) { + // Do nothing + }; + task_custom_node->set_task_func(task_func); + + // Check the task function + auto get_func = task_custom_node->get_task_func(); + get_func(time::time_t{0}, nullptr); + } + + // Xor gate node type + { + auto xor_gate_node = std::make_shared(0); + + // Check basic properties inherited from Node interface + TESTEQUALS(xor_gate_node->get_id(), 0); + // TESTEQUALS(xor_gate_node->get_type(), node_t::XOR_GATE); + TESTEQUALS(xor_gate_node->get_label(), "ExclusiveGateway"); + TESTEQUALS(xor_gate_node->str(), "ExclusiveGateway (id=0)"); + + auto default_node = std::make_shared(1); + xor_gate_node->set_default(default_node); + + // Check the default node + TESTEQUALS(xor_gate_node->get_default(), default_node); + + auto option1_node = std::make_shared(2); + xor_gate_node->add_output(option1_node, [](const time::time_t &time, const std::shared_ptr & /* entity */) { + return time == time::TIME_ZERO; + }); + + auto option2_node = std::make_shared(3); + xor_gate_node->add_output(option2_node, [](const time::time_t &time, const std::shared_ptr & /* entity */) { + return time == time::TIME_MAX; + }); + + auto conditions = xor_gate_node->get_conditions(); + + // Check the conditions + TESTEQUALS(conditions.size(), 2); + + TESTEQUALS(conditions.at(2)(time::TIME_ZERO, nullptr), true); + TESTEQUALS(conditions.at(3)(time::TIME_ZERO, nullptr), false); + + TESTEQUALS(conditions.at(2)(time::TIME_MAX, nullptr), false); + TESTEQUALS(conditions.at(3)(time::TIME_MAX, nullptr), true); + + // Check if next nodes return correctly + TESTEQUALS(xor_gate_node->next(1), default_node); + TESTEQUALS(xor_gate_node->next(2), option1_node); + TESTEQUALS(xor_gate_node->next(3), option2_node); + + // Check that the node throws errors for invalid output IDs + TESTTHROWS(xor_gate_node->next(999)); + } + + // Xor switch gate node type + { + auto xor_switch_gate_node = std::make_shared(0); + + // Check basic properties inherited from Node interface + TESTEQUALS(xor_switch_gate_node->get_id(), 0); + // TESTEQUALS(xor_switch_gate_node->get_type(), node_t::XOR_SWITCH_GATE); + TESTEQUALS(xor_switch_gate_node->get_label(), "ExclusiveSwitchGateway"); + TESTEQUALS(xor_switch_gate_node->str(), "ExclusiveSwitchGateway (id=0)"); + + auto default_node = std::make_shared(1); + xor_switch_gate_node->set_default(default_node); + + // Check the default node + TESTEQUALS(xor_switch_gate_node->get_default(), default_node); + + auto option1_node = std::make_shared(2); + xor_switch_gate_node->set_output(option1_node, 1); + + auto option2_node = std::make_shared(3); + xor_switch_gate_node->set_output(option2_node, 2); + + auto lookup_func = [](const time::time_t &time, const std::shared_ptr & /* entity */) { + if (time == time::TIME_ZERO) { + return 1; + } + if (time == time::TIME_MAX) { + return 2; + } + + return 0; + }; + xor_switch_gate_node->set_lookup_func(lookup_func); + + // Check the lookup function + TESTEQUALS(xor_switch_gate_node->get_lookup_func()(time::TIME_ZERO, nullptr), 1); + TESTEQUALS(xor_switch_gate_node->get_lookup_func()(time::TIME_MAX, nullptr), 2); + TESTEQUALS(xor_switch_gate_node->get_lookup_func()(time::TIME_MIN, nullptr), 0); + + auto lookup_dict = xor_switch_gate_node->get_lookup_dict(); + + // Check the lookup dict + TESTEQUALS(lookup_dict.size(), 2); + + TESTEQUALS(lookup_dict.at(1), option1_node); + TESTEQUALS(lookup_dict.at(2), option2_node); + + // Check if next nodes return correctly + TESTEQUALS(xor_switch_gate_node->next(1), default_node); + TESTEQUALS(xor_switch_gate_node->next(2), option1_node); + TESTEQUALS(xor_switch_gate_node->next(3), option2_node); + + // Check that the node throws errors for invalid output IDs + TESTTHROWS(xor_switch_gate_node->next(999)); + } + + // Xor event gate node type + { + auto xor_event_gate_node = std::make_shared(0); + + // Check basic properties inherited from Node interface + TESTEQUALS(xor_event_gate_node->get_id(), 0); + // TESTEQUALS(xor_event_gate_node->get_type(), node_t::XOR_EVENT_GATE); + TESTEQUALS(xor_event_gate_node->get_label(), "ExclusiveEventGateway"); + TESTEQUALS(xor_event_gate_node->str(), "ExclusiveEventGateway (id=0)"); + + auto option1_node = std::make_shared(1); + xor_event_gate_node->add_output(option1_node, [](const time::time_t & /* time */, const std::shared_ptr & /* entity */, const std::shared_ptr & /* loop */, const std::shared_ptr & /* state */, size_t /* next_id */) { + return nullptr; + }); + + auto option2_node = std::make_shared(2); + xor_event_gate_node->add_output(option2_node, [](const time::time_t & /* time */, const std::shared_ptr & /* entity */, const std::shared_ptr & /* loop */, const std::shared_ptr & /* state */, size_t /* next_id */) { + return nullptr; + }); + + auto primers = xor_event_gate_node->get_primers(); + + // Check the primers + TESTEQUALS(primers.size(), 2); + + // Check if next nodes return correctly + TESTEQUALS(xor_event_gate_node->next(1), option1_node); + TESTEQUALS(xor_event_gate_node->next(2), option2_node); + + // Check that the node throws errors for invalid output IDs + TESTTHROWS(xor_event_gate_node->next(999)); + } +} + +} // namespace openage::gamestate::activity::tests diff --git a/openage/testing/testlist.py b/openage/testing/testlist.py index 8227537697..1b0b18126a 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -104,6 +104,7 @@ def tests_cpp(): yield "openage::curve::tests::container" yield "openage::curve::tests::curve_types" yield "openage::event::tests::eventtrigger" + yield "openage::gamestate::activity::tests::node_types" def demos_cpp(): From 0253022e8bd2393c838a5579a6a61f9cdcf6a6e3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 05:32:48 +0100 Subject: [PATCH 40/42] gamestate: Make a lookup function for next comand switching. --- .../activity/condition/CMakeLists.txt | 1 + .../condition/next_command_switch.cpp | 25 +++++++++++++++++++ .../activity/condition/next_command_switch.h | 23 +++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 libopenage/gamestate/activity/condition/next_command_switch.cpp create mode 100644 libopenage/gamestate/activity/condition/next_command_switch.h diff --git a/libopenage/gamestate/activity/condition/CMakeLists.txt b/libopenage/gamestate/activity/condition/CMakeLists.txt index aabd159b0c..c4ba029b27 100644 --- a/libopenage/gamestate/activity/condition/CMakeLists.txt +++ b/libopenage/gamestate/activity/condition/CMakeLists.txt @@ -1,4 +1,5 @@ add_sources(libopenage command_in_queue.cpp + next_command_switch.cpp next_command.cpp ) diff --git a/libopenage/gamestate/activity/condition/next_command_switch.cpp b/libopenage/gamestate/activity/condition/next_command_switch.cpp new file mode 100644 index 0000000000..be0afd1543 --- /dev/null +++ b/libopenage/gamestate/activity/condition/next_command_switch.cpp @@ -0,0 +1,25 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "next_command_switch.h" + +#include "gamestate/component/internal/command_queue.h" +#include "gamestate/game_entity.h" + + +namespace openage::gamestate::activity { + +int next_command_switch(const time::time_t &time, + const std::shared_ptr &entity) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + + if (command_queue->get_queue().empty(time)) { + return -1; + } + + auto command = command_queue->get_queue().front(time); + + return static_cast(command->get_type()); +} + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/condition/next_command_switch.h b/libopenage/gamestate/activity/condition/next_command_switch.h new file mode 100644 index 0000000000..ba97d5b322 --- /dev/null +++ b/libopenage/gamestate/activity/condition/next_command_switch.h @@ -0,0 +1,23 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace openage::gamestate { +class GameEntity; + +namespace activity { + +/** + * Returns true if the next command in the queue is an idle command. + */ +int next_command_switch(const time::time_t &time, + const std::shared_ptr &entity); + +} // namespace activity +} // namespace openage::gamestate From 754ad80fa24ab5f68936240d221fb6c77cf41023 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 23:57:37 +0100 Subject: [PATCH 41/42] convert: Use new switch gate for command branching. --- .../conversion/aoc/pregen_processor.py | 86 ++++++++----------- .../convert/service/read/nyan_api_loader.py | 69 +++++++++++++++ 2 files changed, 106 insertions(+), 49 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index eb353b0857..59183b2d0f 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -93,13 +93,13 @@ def generate_activities( ability_parent = "engine.util.activity.node.type.Ability" xor_parent = "engine.util.activity.node.type.XORGate" xor_event_parent = "engine.util.activity.node.type.XOREventGate" + xor_switch_parent = "engine.util.activity.node.type.XORSwitchGate" # Condition types condition_parent = "engine.util.activity.condition.Condition" condition_queue_parent = "engine.util.activity.condition.type.CommandInQueue" - condition_next_move_parent = "engine.util.activity.condition.type.NextCommandMove" - condition_next_apply_parent = ( - "engine.util.activity.condition.type.NextCommandApplyEffect" + condition_next_command_parent = ( + "engine.util.activity.switch_condition.type.NextCommand" ) # ======================================================================= @@ -277,36 +277,40 @@ def generate_activities( branch_raw_api_object = RawAPIObject(branch_ref_in_modpack, "BranchCommand", api_objects) branch_raw_api_object.set_location(unit_forward_ref) - branch_raw_api_object.add_raw_parent(xor_parent) - - condition1_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.NextCommandMove") - condition2_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.NextCommandApplyEffect") - branch_raw_api_object.add_raw_member("next", - [condition1_forward_ref, condition2_forward_ref], - xor_parent) + branch_raw_api_object.add_raw_parent(xor_switch_parent) + + switch_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.NextCommandSwitch") + branch_raw_api_object.add_raw_member("switch", + switch_forward_ref, + xor_switch_parent) idle_forward_ref = ForwardRef(pregen_converter_group, "util.activity.types.Unit.Idle") branch_raw_api_object.add_raw_member("default", idle_forward_ref, - xor_parent) + xor_switch_parent) pregen_converter_group.add_raw_api_object(branch_raw_api_object) pregen_nyan_objects.update({branch_ref_in_modpack: branch_raw_api_object}) - # condition for branching to apply effect - condition_ref_in_modpack = "util.activity.types.Unit.NextCommandApplyEffect" + # condition for branching based on command + condition_ref_in_modpack = "util.activity.types.Unit.NextCommandSwitch" condition_raw_api_object = RawAPIObject(condition_ref_in_modpack, - "NextCommandApplyEffect", api_objects) + "NextCommandSwitch", api_objects) condition_raw_api_object.set_location(branch_forward_ref) - condition_raw_api_object.add_raw_parent(condition_next_apply_parent) + condition_raw_api_object.add_raw_parent(condition_next_command_parent) apply_effect_forward_ref = ForwardRef(pregen_converter_group, "util.activity.types.Unit.ApplyEffect") + move_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Move") + next_nodes_lookup = { + api_objects["engine.util.command.type.ApplyEffect"]: apply_effect_forward_ref, + api_objects["engine.util.command.type.Move"]: move_forward_ref, + } condition_raw_api_object.add_raw_member("next", - apply_effect_forward_ref, - condition_parent) + next_nodes_lookup, + condition_next_command_parent) pregen_converter_group.add_raw_api_object(condition_raw_api_object) pregen_nyan_objects.update({condition_ref_in_modpack: condition_raw_api_object}) @@ -329,22 +333,6 @@ def generate_activities( pregen_converter_group.add_raw_api_object(apply_effect_raw_api_object) pregen_nyan_objects.update({apply_effect_ref_in_modpack: apply_effect_raw_api_object}) - # condition for branching to move - condition_ref_in_modpack = "util.activity.types.Unit.NextCommandMove" - condition_raw_api_object = RawAPIObject(condition_ref_in_modpack, - "NextCommandMove", api_objects) - condition_raw_api_object.set_location(branch_forward_ref) - condition_raw_api_object.add_raw_parent(condition_next_move_parent) - - move_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.Move") - condition_raw_api_object.add_raw_member("next", - move_forward_ref, - condition_parent) - - pregen_converter_group.add_raw_api_object(condition_raw_api_object) - pregen_nyan_objects.update({condition_ref_in_modpack: condition_raw_api_object}) - # Move move_ref_in_modpack = "util.activity.types.Unit.Move" move_raw_api_object = RawAPIObject(move_ref_in_modpack, @@ -394,7 +382,7 @@ def generate_activities( pregen_converter_group.add_raw_api_object(end_raw_api_object) pregen_nyan_objects.update({end_ref_in_modpack: end_raw_api_object}) - @staticmethod + @ staticmethod def generate_attributes( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -507,7 +495,7 @@ def generate_attributes( pregen_converter_group.add_raw_api_object(faith_abbrv_value) pregen_nyan_objects.update({faith_abbrv_ref_in_modpack: faith_abbrv_value}) - @staticmethod + @ staticmethod def generate_diplomatic_stances( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -582,7 +570,7 @@ def generate_diplomatic_stances( pregen_converter_group.add_raw_api_object(gaia_raw_api_object) pregen_nyan_objects.update({gaia_ref_in_modpack: gaia_raw_api_object}) - @staticmethod + @ staticmethod def generate_team_property( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -621,7 +609,7 @@ def generate_team_property( stances, "engine.util.patch.property.type.Diplomatic") - @staticmethod + @ staticmethod def generate_entity_types( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -748,7 +736,7 @@ def generate_entity_types( pregen_converter_group.add_raw_api_object(new_game_entity_type) pregen_nyan_objects.update({class_obj_name: new_game_entity_type}) - @staticmethod + @ staticmethod def generate_effect_types( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -894,7 +882,7 @@ def generate_effect_types( pregen_converter_group.add_raw_api_object(type_raw_api_object) pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - @staticmethod + @ staticmethod def generate_exchange_objects( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -1187,7 +1175,7 @@ def generate_exchange_objects( pregen_converter_group.add_raw_api_object(price_mode_raw_api_object) pregen_nyan_objects.update({price_mode_ref_in_modpack: price_mode_raw_api_object}) - @staticmethod + @ staticmethod def generate_formation_types( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -1400,7 +1388,7 @@ def generate_formation_types( pregen_converter_group.add_raw_api_object(subformation_raw_api_object) pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) - @staticmethod + @ staticmethod def generate_language_objects( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -1441,7 +1429,7 @@ def generate_language_objects( pregen_converter_group.add_raw_api_object(language_raw_api_object) pregen_nyan_objects.update({language_ref_in_modpack: language_raw_api_object}) - @staticmethod + @ staticmethod def generate_misc_effect_objects( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -1763,7 +1751,7 @@ def generate_misc_effect_objects( calc_forward_ref, "engine.resistance.property.type.Stacked") - @staticmethod + @ staticmethod def generate_modifiers( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -1919,7 +1907,7 @@ def generate_modifiers( properties, modifier_parent) - @staticmethod + @ staticmethod def generate_terrain_types( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -1959,7 +1947,7 @@ def generate_terrain_types( pregen_converter_group.add_raw_api_object(type_raw_api_object) pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - @staticmethod + @ staticmethod def generate_path_types( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -2024,7 +2012,7 @@ def generate_path_types( pregen_converter_group.add_raw_api_object(path_type_raw_api_object) pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) - @staticmethod + @ staticmethod def generate_resources( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup @@ -2224,7 +2212,7 @@ def generate_resources( pregen_converter_group.add_raw_api_object(pop_name_value) pregen_nyan_objects.update({pop_name_ref_in_modpack: pop_name_value}) - @staticmethod + @ staticmethod def generate_death_condition( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index 4a4396d425..0b8d6c87e7 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -637,6 +637,27 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.util.activity.node.type.XORSwitchGate + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("XORSwitchGate", parents) + fqon = "engine.util.activity.node.type.XORSwitchGate" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.switch_condition.SwitchCondition + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("SwitchCondition", parents) + fqon = "engine.util.activity.switch_condition.SwitchCondition" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.switch_condition.type.NextCommand + parents = [api_objects["engine.util.activity.switch_condition.SwitchCondition"]] + nyan_object = NyanObject("NextCommand", parents) + fqon = "engine.util.activity.switch_condition.type.NextCommand" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.util.animation_override.AnimationOverride parents = [api_objects["engine.root.Object"]] nyan_object = NyanObject("AnimationOverride", parents) @@ -735,6 +756,34 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.util.command.Command + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Command", parents) + fqon = "engine.util.command.Command" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.command.type.ApplyEffect + parents = [api_objects["engine.util.command.Command"]] + nyan_object = NyanObject("ApplyEffect", parents) + fqon = "engine.util.command.type.ApplyEffect" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.command.type.Idle + parents = [api_objects["engine.util.command.Command"]] + nyan_object = NyanObject("Idle", parents) + fqon = "engine.util.command.type.Idle" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.command.type.Move + parents = [api_objects["engine.util.command.Command"]] + nyan_object = NyanObject("Move", parents) + fqon = "engine.util.command.type.Move" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.util.container_type.SendToContainerType parents = [api_objects["engine.root.Object"]] nyan_object = NyanObject("SendToContainerType", parents) @@ -3331,6 +3380,26 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("default", member_type, None, None, 0) api_object.add_member(member) + # engine.util.activity.node.type.XORSwitchGate + api_object = api_objects["engine.util.activity.node.type.XORSwitchGate"] + + member_type = NyanMemberType(api_objects["engine.util.activity.switch_condition.SwitchCondition"]) + member = NyanMember("switch", member_type, None, None, 0) + api_object.add_member(member) + member_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member = NyanMember("default", member_type, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.switch_condition.type.NextCommand + api_object = api_objects["engine.util.activity.switch_condition.type.NextCommand"] + + subtype = NyanMemberType(api_objects["engine.util.command.Command"]) + key_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) + value_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member_type = NyanMemberType(MemberType.DICT, (key_type, value_type)) + member = NyanMember("next", member_type, None, None, 0) + api_object.add_member(member) + # engine.util.animation_override.AnimationOverride api_object = api_objects["engine.util.animation_override.AnimationOverride"] From 63bc3826a75d605a9d30357b1abb386561d8db8a Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 7 Nov 2024 22:34:19 +0100 Subject: [PATCH 42/42] gamestate: Init new XorSwitchGate activity node type from nyan. --- libopenage/gamestate/api/activity.cpp | 23 +++++++++++++++++++++++ libopenage/gamestate/api/definitions.h | 22 +++++++++++++++++++++- libopenage/gamestate/api/types.h | 7 +++++++ libopenage/gamestate/entity_factory.cpp | 4 ++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index edd596abe8..0b1b1c1fd9 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -78,6 +78,29 @@ std::vector APIActivityNode::get_next(const nyan::Object &node) { return next_nodes; } + case activity::node_t::XOR_SWITCH_GATE: { + auto switch_condition = node.get("XORSwitchGate.switch"); + std::shared_ptr db_view = node.get_view(); + + auto switch_condition_obj = db_view->get_object(switch_condition->get_name()); + auto switch_condition_parent = switch_condition_obj.get_parents()[0]; + auto switch_condition_type = ACTIVITY_SWITCH_CONDITION_TYPES.get(switch_condition_parent); + + switch (switch_condition_type) { + case switch_condition_t::NEXT_COMMAND: { + auto next = switch_condition_obj.get_dict("NextCommand.next"); + std::vector next_nodes; + for (auto next_node : next) { + auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); + next_nodes.push_back(db_view->get_object(next_node_value->get_name())); + } + + return next_nodes; + } + default: + throw Error(MSG(err) << "Unknown switch condition type."); + } + } default: throw Error(MSG(err) << "Unknown activity node type."); } diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index ce4ee3e7a3..fa831b0e4a 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -10,11 +10,13 @@ #include "datastructure/constexpr_map.h" #include "gamestate/activity/condition/command_in_queue.h" #include "gamestate/activity/condition/next_command.h" +#include "gamestate/activity/condition/next_command_switch.h" #include "gamestate/activity/event/command_in_queue.h" #include "gamestate/activity/event/wait.h" #include "gamestate/activity/types.h" #include "gamestate/activity/xor_event_gate.h" #include "gamestate/activity/xor_gate.h" +#include "gamestate/activity/xor_switch_gate.h" #include "gamestate/api/types.h" #include "gamestate/system/types.h" @@ -237,7 +239,9 @@ static const auto ACTIVITY_NODE_DEFS = datastructure::create_const_map( std::pair("engine.util.activity.event.type.CommandInQueue", std::function(gamestate::activity::primer_command_in_queue)), @@ -273,6 +280,19 @@ static const auto ACTIVITY_EVENT_PRIMERS = datastructure::create_const_map( + std::pair("engine.util.activity.switch_condition.type.NextCommand", + std::function(gamestate::activity::next_command_switch))); + +static const auto ACTIVITY_SWITCH_CONDITION_TYPES = datastructure::create_const_map( + std::pair("engine.util.activity.switch_condition.type.NextCommand", + switch_condition_t::NEXT_COMMAND)); + /** * Maps internal patch property types to nyan API values. */ diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index 7eeb141e71..7b218afd47 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -81,4 +81,11 @@ enum class patch_property_t { DIPLOMATIC, }; +/** + * Types of conditions for the XORSwitchGate API activity node. + */ +enum class switch_condition_t { + NEXT_COMMAND, +}; + } // namespace openage::gamestate::api diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 6907e8aae9..18e40f52ff 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -22,6 +22,7 @@ #include "gamestate/activity/task_system_node.h" #include "gamestate/activity/xor_event_gate.h" #include "gamestate/activity/xor_gate.h" +#include "gamestate/activity/xor_switch_gate.h" #include "gamestate/api/activity.h" #include "gamestate/component/api/apply_effect.h" #include "gamestate/component/api/idle.h" @@ -295,6 +296,9 @@ void EntityFactory::init_activity(const std::shared_ptr(node_id); break; + case activity::node_t::XOR_SWITCH_GATE: + node_id_map[node_id] = std::make_shared(node_id); + break; default: throw Error{ERR << "Unknown activity node type of node: " << node.get_name()}; }