From 959893a91b77195c88db4af7925db26e23b6aa3f Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:16:59 +0200 Subject: [PATCH 001/118] 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 94238b674a99e20be99c7a3c7d4ca571c70977be Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:17:34 +0200 Subject: [PATCH 002/118] 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 6e1a4c825f..70dc8ee96d 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2025 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 404da77dcfa39a3560af4f4e6be52b22be16895e Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:46:56 +0200 Subject: [PATCH 003/118] 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 70dc8ee96d..8d1a83bd61 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 5d57aa9e08a8c2407a4d387aef26d99282b3173a Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:49:41 +0200 Subject: [PATCH 004/118] 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 a38ed0648cfe3f5e1ea08872c73ad488b07541c0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:04:54 +0200 Subject: [PATCH 005/118] 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 8d1a83bd61..03c2c168c2 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 6bf6a28d65f863bbf93e49a2aec5d4c9a42dcb40 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:06:04 +0200 Subject: [PATCH 006/118] 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 542748dc5c9458ad2181a4c51eba66a5d00d465f Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:12:37 +0200 Subject: [PATCH 007/118] 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 1b4b287217cd39f1de650aa20a244a5e5d24635d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:45:30 +0200 Subject: [PATCH 008/118] 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 d08c22f3392f9b48321ac81f4d799ba5da0d70e2 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 04:29:14 +0200 Subject: [PATCH 009/118] 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 bddc34b2d57cd77f4a4fcba112b9d8972ee4c52e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 01:11:03 +0200 Subject: [PATCH 010/118] 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 03c2c168c2..a1c505ac21 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 0aaaefc13ef3fc9bce27f4912d98453111bfeed2 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 01:15:08 +0200 Subject: [PATCH 011/118] 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 fd3101949c8f4d09f3fb22a7368d293461b9d0d1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 01:19:58 +0200 Subject: [PATCH 012/118] 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 3c7ba1b04410afaaedeb5fb7e9275fa4626dbcb9 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 17:29:25 +0200 Subject: [PATCH 013/118] gamestate: Allow fractional values for attributes. --- libopenage/gamestate/component/api/live.cpp | 24 ++++++++++----------- libopenage/gamestate/component/api/live.h | 22 +++++++++++++------ libopenage/gamestate/component/types.h | 7 ++++++ libopenage/gamestate/entity_factory.cpp | 11 +++++----- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/libopenage/gamestate/component/api/live.cpp b/libopenage/gamestate/component/api/live.cpp index f7a1f17d98..8b6ccbe6ec 100644 --- a/libopenage/gamestate/component/api/live.cpp +++ b/libopenage/gamestate/component/api/live.cpp @@ -1,12 +1,10 @@ -// Copyright 2021-2025 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/container/iterator.h" #include "curve/container/map_filter_iterator.h" -#include "curve/discrete.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 4916713cdc..349be191f4 100644 --- a/libopenage/gamestate/component/api/live.h +++ b/libopenage/gamestate/component/api/live.h @@ -1,4 +1,4 @@ -// Copyright 2021-2025 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -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 a1c505ac21..c092c23908 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 9bda7d3ec36e7eb8e050f316e75ad3302451cdc8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 21:57:15 +0200 Subject: [PATCH 014/118] 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 39f9378504750336776883af52313ca630bcb80c Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 13 Sep 2024 23:48:54 +0200 Subject: [PATCH 015/118] 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 c092c23908..a17f2f34e1 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 b7aa43f0f656a03f1dfd967a20e101466b83e4da Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 14 Sep 2024 13:49:26 +0200 Subject: [PATCH 016/118] 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 6c4c3b33f839f1baf23a1227cde6c2c1ee9f017c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 14 Sep 2024 13:51:26 +0200 Subject: [PATCH 017/118] 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 | 4 ++-- 7 files changed, 22 insertions(+), 22 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 5a44baf113..8d9fcf8d5c 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2025 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "move.h" @@ -80,7 +80,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 1b9da5539eb06ba6cf278a76531a362c73ddf53f Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 06:18:08 +0200 Subject: [PATCH 018/118] 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 64dcfb8db1259d6095b00620e455aaee4c083866 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:25:58 +0200 Subject: [PATCH 019/118] 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 17165b7b1854598885286e8d491e52b174c15b29 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:27:03 +0200 Subject: [PATCH 020/118] 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 04bf4c8d1e84a1f6e887e5e1f48d703876d1e591 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:38:11 +0200 Subject: [PATCH 021/118] 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 826d498d5513432c8550700d6b6f3fc5001e9b32 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:52:51 +0200 Subject: [PATCH 022/118] gamestate: Move animation property handling to helper function. --- libopenage/gamestate/system/CMakeLists.txt | 1 + libopenage/gamestate/system/idle.cpp | 17 ++++-------- libopenage/gamestate/system/move.cpp | 11 ++------ libopenage/gamestate/system/property.cpp | 32 ++++++++++++++++++++++ libopenage/gamestate/system/property.h | 31 +++++++++++++++++++++ 5 files changed, 71 insertions(+), 21 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..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" @@ -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 8d9fcf8d5c..eccd97694a 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" @@ -173,15 +174,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 6718a85f5372f6b3bfd1b4a48c2a0a2679fa6cb7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:53:09 +0200 Subject: [PATCH 023/118] 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 7c64860dd89f6d15f6a49400dba467505aa8ba75 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 16 Oct 2024 07:39:09 +0200 Subject: [PATCH 024/118] curve: Add compress argument for curve operations. --- libopenage/curve/base_curve.h | 62 +++++++++--- libopenage/curve/continuous.h | 16 +++- libopenage/curve/discrete_mod.h | 22 +++-- libopenage/curve/keyframe_container.h | 133 +++++++++++++++++++++----- 4 files changed, 187 insertions(+), 46 deletions(-) diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index de5c14201d..d7cb1d4fd7 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -1,4 +1,4 @@ -// Copyright 2017-2025 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -74,24 +74,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. @@ -113,9 +137,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. @@ -130,11 +158,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. @@ -200,7 +232,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 @@ -218,7 +252,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()) { @@ -229,7 +265,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); } @@ -283,7 +321,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); @@ -302,7 +341,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 8434942058..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); } @@ -390,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 799b98e5a0eee54bdb93f2a2173a11ca478f6fe0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 16 Oct 2024 07:45:57 +0200 Subject: [PATCH 025/118] 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 bfc62b63750a49411830f6af4bef0f73a70287e1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 16 Oct 2024 22:11:40 +0200 Subject: [PATCH 026/118] 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 d7cb1d4fd7..bb7c7608bf 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -2,6 +2,7 @@ #pragma once +#include #include #include #include @@ -110,12 +111,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. @@ -244,6 +242,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; @@ -255,19 +260,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 b2ca950a519414bde3f3788b866fddc90e4c15c4 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 18 Oct 2024 06:39:45 +0200 Subject: [PATCH 027/118] curve: Compress method for curves. --- libopenage/curve/base_curve.h | 11 +++++++++ libopenage/curve/discrete.h | 14 ++++++++++- libopenage/curve/interpolated.h | 43 +++++++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index bb7c7608bf..275ba4be13 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -120,6 +120,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 b9f9b6b00c..5449f0845c 100644 --- a/libopenage/curve/discrete.h +++ b/libopenage/curve/discrete.h @@ -1,4 +1,4 @@ -// Copyright 2017-2025 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -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 4e935e7cd017cc310bb95ed22e768143317fac05 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 18 Oct 2024 23:11:59 +0200 Subject: [PATCH 028/118] curve: Add new unit tests for compress() method. --- libopenage/curve/tests/curve_types.cpp | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/libopenage/curve/tests/curve_types.cpp b/libopenage/curve/tests/curve_types.cpp index 935aa7141d..3a0ecb9b0e 100644 --- a/libopenage/curve/tests/curve_types.cpp +++ b/libopenage/curve/tests/curve_types.cpp @@ -232,6 +232,42 @@ void curve_types() { TESTEQUALS(c.get(8), 4); } + { + // 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(); @@ -257,6 +293,40 @@ void curve_types() { TESTEQUALS(complex.get(10), "Test 10"); } + { + // 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(); From 134c65e04a2e867a4361c4f5d01920dfe0cae1e9 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 18 Oct 2024 23:12:17 +0200 Subject: [PATCH 029/118] 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 5449f0845c..50f3f5b4cf 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 d968adcb3c16609c72b8ec3bc516b5c619c6ddfc Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 19 Oct 2024 22:04:39 +0200 Subject: [PATCH 030/118] 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 85f6ce6fc92c751fe5a193740b29587505ff5e5c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 19 Oct 2024 22:09:14 +0200 Subject: [PATCH 031/118] 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 275ba4be13..dfe9b5982f 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -355,6 +355,10 @@ void BaseCurve::sync(const BaseCurve &other, this->set_insert(start, get_other); } + if (compress) { + this->compress(start); + } + this->changes(start); } @@ -375,6 +379,10 @@ void BaseCurve::sync(const BaseCurve &other, this->set_insert(start, get_other); } + if (compress) { + this->compress(start); + } + this->changes(start); } From 0920ce52b3401675904dec9c720a09722a0b3eb5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 00:23:26 +0200 Subject: [PATCH 032/118] 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 c1ffbcea659ceffc53a45902daedd90abf337836 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 15:04:23 +0200 Subject: [PATCH 033/118] renderer: Make fetching from render entity more reliable. --- libopenage/curve/keyframe_container.h | 23 +++++++------ 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 ------ 12 files changed, 89 insertions(+), 97 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/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 ee36258aa218070d60d2e5feb41014189c80cd91 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 17:12:10 +0200 Subject: [PATCH 034/118] 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 80ddc6003118c56e30a51390acc5f0c15ce4e44e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 19:51:42 +0200 Subject: [PATCH 035/118] 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/container/iterator.h | 5 +-- libopenage/curve/container/map.h | 29 ++++++++-------- .../curve/container/map_filter_iterator.h | 7 ++-- libopenage/curve/container/queue.h | 27 +++++++-------- .../curve/container/queue_filter_iterator.h | 5 +-- libopenage/curve/continuous.h | 9 ++--- libopenage/curve/discrete.h | 18 ++++------ libopenage/curve/discrete_mod.h | 22 +++++-------- libopenage/curve/interpolated.h | 9 ++--- libopenage/curve/keyframe.h | 5 +-- libopenage/curve/keyframe_container.h | 33 ++++++++++--------- libopenage/curve/segmented.h | 9 ++--- libopenage/gamestate/component/api/live.h | 2 +- 17 files changed, 130 insertions(+), 102 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 eb52858f43..05082fe4a8 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 dfe9b5982f..0996f38a8b 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, @@ -171,7 +172,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, @@ -240,7 +241,7 @@ class BaseCurve : public event::EventEntity { }; -template +template void BaseCurve::set_last(const time::time_t &at, const T &value, bool compress) { @@ -267,7 +268,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) { @@ -287,7 +288,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); @@ -295,14 +296,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); @@ -310,7 +311,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++; @@ -318,7 +319,7 @@ std::pair BaseCurve::next_frame(const time::time_t &ti return elem.as_pair(); } -template +template std::string BaseCurve::str() const { std::stringstream ss; ss << "Curve[" << this->idstr() << "]{" << std::endl; @@ -330,7 +331,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) { @@ -341,7 +342,7 @@ void BaseCurve::check_integrity() const { } } -template +template void BaseCurve::sync(const BaseCurve &other, const time::time_t &start, bool compress) { @@ -363,8 +364,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/container/iterator.h b/libopenage/curve/container/iterator.h index 7a4fb82d6b..dd7c5f29eb 100644 --- a/libopenage/curve/container/iterator.h +++ b/libopenage/curve/container/iterator.h @@ -1,7 +1,8 @@ -// Copyright 2017-2025 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once +#include "curve/concept.h" #include "time/time.h" #include "util/fixed_point.h" @@ -11,7 +12,7 @@ namespace openage::curve { /** * Default interface for curve containers */ -template class CurveIterator { diff --git a/libopenage/curve/container/map.h b/libopenage/curve/container/map.h index 4997824a6d..11913db4f6 100644 --- a/libopenage/curve/container/map.h +++ b/libopenage/curve/container/map.h @@ -1,4 +1,4 @@ -// Copyright 2017-2025 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -7,6 +7,7 @@ #include #include +#include "curve/concept.h" #include "curve/container/element_wrapper.h" #include "curve/container/map_filter_iterator.h" #include "time/time.h" @@ -19,7 +20,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 { /** * Data holder. Maps keys to map elements. @@ -72,14 +73,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 { @@ -96,7 +97,7 @@ UnorderedMap::at(const time::time_t &time, } } -template +template MapFilterIterator> UnorderedMap::begin(const time::time_t &time) const { return MapFilterIterator>( @@ -106,7 +107,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>( @@ -116,7 +117,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>( @@ -131,7 +132,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, @@ -143,7 +144,7 @@ UnorderedMap::insert(const time::time_t &alive, value); } -template +template MapFilterIterator> UnorderedMap::insert(const time::time_t &alive, const time::time_t &dead, @@ -158,7 +159,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); @@ -167,13 +168,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); @@ -182,13 +183,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/container/map_filter_iterator.h b/libopenage/curve/container/map_filter_iterator.h index c9afceee88..7fd93cb6e3 100644 --- a/libopenage/curve/container/map_filter_iterator.h +++ b/libopenage/curve/container/map_filter_iterator.h @@ -1,7 +1,8 @@ -// Copyright 2017-2025 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once +#include "curve/concept.h" #include "curve/container/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/container/queue.h b/libopenage/curve/container/queue.h index fb32a53cbb..6d33a54627 100644 --- a/libopenage/curve/container/queue.h +++ b/libopenage/curve/container/queue.h @@ -1,4 +1,4 @@ -// Copyright 2017-2025 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -11,6 +11,7 @@ #include "error/error.h" +#include "curve/concept.h" #include "curve/container/element_wrapper.h" #include "curve/container/iterator.h" #include "curve/container/queue_filter_iterator.h" @@ -31,7 +32,7 @@ namespace curve { * time it will happen. * This container can be used to store interactions */ -template +template class Queue : public event::EventEntity { public: /** @@ -242,7 +243,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; @@ -266,7 +267,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(), @@ -281,7 +282,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(), @@ -307,7 +308,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; @@ -317,7 +318,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) { @@ -333,7 +334,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(), @@ -343,7 +344,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>( @@ -358,20 +359,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(); @@ -415,7 +416,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/container/queue_filter_iterator.h b/libopenage/curve/container/queue_filter_iterator.h index 6b2fa471f2..a56a5afb44 100644 --- a/libopenage/curve/container/queue_filter_iterator.h +++ b/libopenage/curve/container/queue_filter_iterator.h @@ -1,7 +1,8 @@ -// Copyright 2017-2025 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once +#include "curve/concept.h" #include "curve/container/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/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 50f3f5b4cf..959ac36cd6 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/keyframe.h b/libopenage/curve/keyframe.h index cd2ebf6ced..70d2b34c77 100644 --- a/libopenage/curve/keyframe.h +++ b/libopenage/curve/keyframe.h @@ -1,7 +1,8 @@ -// Copyright 2019-2025 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once +#include "curve/concept.h" #include "time/time.h" #include "util/fixed_point.h" @@ -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/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 349be191f4..96f474a818 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 50db1d986ebc741083d579fbaad76171a6c33f80 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 22 Oct 2024 06:06:49 +0200 Subject: [PATCH 036/118] doc: Add documentation for curve compression. --- doc/code/curves.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/doc/code/curves.md b/doc/code/curves.md index e914f8bcfe..3d3cdd2613 100644 --- a/doc/code/curves.md +++ b/doc/code/curves.md @@ -18,6 +18,7 @@ Curves are an integral part of openage's event-based game simulation. 1. [Queue](#queue) 2. [Unordered Map](#unordered-map) 3. [Array](#array) +4. [Compression](#compression) ## Motivation @@ -133,6 +134,9 @@ Modify operations insert values for a specific point in time. | `set_insert(t, value)` | Insert a new keyframe value at time `t` | | `set_last(t, value)` | Insert a new keyframe value at time `t`; delete all keyframes after time `t` | | `set_replace(t, value)` | Insert a new keyframe value at time `t`; remove all other keyframes with time `t` | +| `compress(t)` | Remove redundant keyframes at and after time `t`; see [Compression] for more info | + +[Compression]: #compression **Copy** @@ -292,3 +296,28 @@ Modify operations insert values for a specific point in time. | Method | Description | | ---------------- | ------------------------------------------------------------------------------------------------ | | `sync(Curve, t)` | Replace all keyframes from self after time `t` with keyframes from source `Curve` after time `t` | + + +## Compression + +Curves support basic lossless compression by removing redundant keyframes from the curve. +Keyframes are considered redundant if they do not change any interpolation results, i.e. +the result of `get(t)` does not change. + +The most straight-forward way to use compression with primitive curves is the `compress(t)` +method. `compress(t)` iterates over the curve and removes all redundant keyframes after +or at time `t`. The runtime has linear complexity `O(n)` based on the number of elements +in the keyframe container. + +Furthermore, primitive curves support incremental compression during insertion for the +`set_insert(t, value)` and `set_last(t, value)` methods via their `compress` argument. +If compression is active, `(t, value)` is only inserted when it is not a redundant +keyframe. `sync(Curve, t)` also supports compression with a flag `compress` passed as +an argument. + +Compression may be used in cases where the size should be kept small, e.g. when the curve +is tranferred via network or recorded in a replay file. Another application of compression +is in the [renderer](/doc/code/renderer/README.md) for the discrete curves storing an object's +animations. Since compression removes redundant animation entries, the renderer can determine +when the current animation has started much easier as this is then returned by the keyframe +time in `frame(t)`. From 2e199406580826932d3bc9498f5c5dbf5e6a23eb Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 4 Nov 2024 02:00:19 +0100 Subject: [PATCH 037/118] 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 | 3 +- .../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, 219 insertions(+), 11 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..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 @@ -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 b4b533f428959ad52a2bc87be5956dedfaa78b7d Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 04:23:10 +0100 Subject: [PATCH 038/118] gamestate: Handle XorSwichGate in activity system. --- libopenage/gamestate/system/activity.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 d0d1c858e37687a264a6dfb7ba4e4a70e0032746 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 05:11:38 +0100 Subject: [PATCH 039/118] 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 7b3f8a580a5f18c866fc2113fa87f0c010eadbe8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 05:32:48 +0100 Subject: [PATCH 040/118] 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 c3adc76dd8c7030dddf724127db0ac15d03244f1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 23:57:37 +0100 Subject: [PATCH 041/118] convert: Use new switch gate for command branching. --- .../conversion/aoc/pregen_processor.py | 58 +++++++--------- .../convert/service/read/nyan_api_loader.py | 69 +++++++++++++++++++ 2 files changed, 92 insertions(+), 35 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index eb353b0857..77ac354938 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, 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 a2a843b42bcf27611c0b02da17c0dda235068750 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 7 Nov 2024 22:34:19 +0100 Subject: [PATCH 042/118] gamestate: Init new XorSwitchGate activity node type from nyan. --- libopenage/gamestate/api/activity.cpp | 25 ++++++++++++++++++++++++- libopenage/gamestate/api/definitions.h | 22 +++++++++++++++++++++- libopenage/gamestate/api/types.h | 7 +++++++ libopenage/gamestate/entity_factory.cpp | 4 ++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index edd596abe8..34059223dc 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.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 "activity.h" @@ -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 a17f2f34e1..49cdc0fdac 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()}; } From 45210ea3b5d8e5f830468849ce909f928fffb11f Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 8 Apr 2025 00:07:12 +0200 Subject: [PATCH 043/118] gamestate: Resolve lookup func and node ID mapping for switch condition. --- libopenage/gamestate/api/activity.cpp | 41 ++++++++++++++++++++++++- libopenage/gamestate/api/activity.h | 39 ++++++++++++++++++++++- libopenage/gamestate/api/definitions.h | 17 +++++++++- libopenage/gamestate/entity_factory.cpp | 23 +++++++++++++- 4 files changed, 116 insertions(+), 4 deletions(-) diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index 34059223dc..3931ae2638 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "activity.h" @@ -126,6 +126,45 @@ activity::condition_t APIActivityCondition::get_condition(const nyan::Object &co return ACTIVITY_CONDITIONS.get(immediate_parent); } +bool APIActivitySwitchCondition::is_switch_condition(const nyan::Object &obj) { + nyan::fqon_t immediate_parent = obj.get_parents()[0]; + return immediate_parent == "engine.util.activity.switch_condition.SwitchCondition"; +} + +activity::XorSwitchGate::lookup_function_t APIActivitySwitchCondition::get_lookup(const nyan::Object &condition) { + nyan::fqon_t immediate_parent = condition.get_parents()[0]; + return ACTIVITY_SWITCH_CONDITIONS.get(immediate_parent); +} + +APIActivitySwitchCondition::lookup_map_t APIActivitySwitchCondition::get_lookup_map(const nyan::Object &condition) { + nyan::fqon_t immediate_parent = condition.get_parents()[0]; + auto switch_condition_type = ACTIVITY_SWITCH_CONDITION_TYPES.get(immediate_parent); + + switch (switch_condition_type) { + case switch_condition_t::NEXT_COMMAND: { + auto next = condition.get("NextCommand.next"); + lookup_map_t lookup_map{}; + for (auto next_node : next->get()) { + auto key_value = std::dynamic_pointer_cast(next_node.first.get_ptr()); + auto key_obj = condition.get_view()->get_object(key_value->get_name()); + + // Get engine lookup key value + auto key = static_cast(COMMAND_DEFS.get(key_obj.get_name())); + + // Get node ID + auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); + auto next_node_id = next_node_value->get_name(); + + lookup_map[key] = next_node_id; + } + + return lookup_map; + } + default: + throw Error(MSG(err) << "Unknown switch condition type."); + } +} + bool APIActivityEvent::is_event(const nyan::Object &obj) { nyan::fqon_t immediate_parent = obj.get_parents()[0]; return immediate_parent == "engine.util.activity.event.Event"; diff --git a/libopenage/gamestate/api/activity.h b/libopenage/gamestate/api/activity.h index 2001aa7548..4413585ec5 100644 --- a/libopenage/gamestate/api/activity.h +++ b/libopenage/gamestate/api/activity.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -9,6 +9,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/system/types.h" @@ -109,6 +110,42 @@ class APIActivityCondition { static activity::condition_t get_condition(const nyan::Object &condition); }; +/** + * Helper class for creating Activity switch condition objects from the nyan API. + */ +class APIActivitySwitchCondition { +public: + /** + * Check if a nyan object is a switch condition (type == \p engine.util.activity.switch_condition.SwitchCondition). + * + * @param obj nyan object. + * + * @return true if the object is a switch condition, else false. + */ + static bool is_switch_condition(const nyan::Object &obj); + + /** + * Get the lookup function for a switch condition. + * + * @param condition nyan object. + * + * @return Lookup function. + */ + static activity::XorSwitchGate::lookup_function_t get_lookup(const nyan::Object &condition); + + using lookup_map_t = std::unordered_map; + + /** + * Get the mapping of lookup keys to output node IDs. Lookup keys are resolved from nyan API + * mappings to the engine's lookup key type. + * + * @param condition nyan object. + * + * @return Mapping of lookup keys to output node IDs. + */ + static lookup_map_t get_lookup_map(const nyan::Object &condition); +}; + /** * Helper class for creating Activity event objects from the nyan API. */ diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index fa831b0e4a..aa129db6be 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -18,6 +18,7 @@ #include "gamestate/activity/xor_gate.h" #include "gamestate/activity/xor_switch_gate.h" #include "gamestate/api/types.h" +#include "gamestate/component/internal/commands/types.h" #include "gamestate/system/types.h" @@ -288,6 +289,9 @@ static const auto ACTIVITY_SWITCH_CONDITIONS = datastructure::create_const_map( std::pair("engine.util.activity.switch_condition.type.NextCommand", @@ -300,4 +304,15 @@ static const auto PATCH_PROPERTY_DEFS = datastructure::create_const_map("engine.patch.property.type.Diplomatic")))); +/** + * Maps API command types to engine command types. + */ +static const auto COMMAND_DEFS = datastructure::create_const_map( + std::pair("engine.util.command.type.Idle", + component::command::command_t::IDLE), + std::pair("engine.util.command.type.Move", + component::command::command_t::MOVE), + std::pair("engine.util.command.type.ApplyEffect", + component::command::command_t::APPLY_EFFECT)); + } // namespace openage::gamestate::api diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 49cdc0fdac..dcd59293a4 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "entity_factory.h" @@ -372,6 +372,27 @@ void EntityFactory::init_activity(const std::shared_ptr(activity_node); + auto switch_value = nyan_node.get("XORSwitchGate.switch"); + auto switch_obj = owner_db_view->get_object(switch_value->get_name()); + + auto lookup_func = api::APIActivitySwitchCondition::get_lookup(switch_obj); + xor_switch_gate->set_lookup_func(lookup_func); + + auto lookup_map = api::APIActivitySwitchCondition::get_lookup_map(switch_obj); + for (const auto &[key, node_id] : lookup_map) { + auto output_id = visited[node_id]; + auto output_node = node_id_map[output_id]; + xor_switch_gate->set_output(output_node, key); + } + + auto default_fqon = nyan_node.get("XORSwitchGate.default")->get_name(); + auto default_id = visited[default_fqon]; + auto default_node = node_id_map[default_id]; + xor_switch_gate->set_default(default_node); + break; + } default: throw Error{ERR << "Unknown activity node type of node: " << current_node.first}; } From c9522286358993fccfc9eb968508640a841d02f5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 8 Apr 2025 00:54:21 +0200 Subject: [PATCH 044/118] gamestate: Correctly subtract/add applied attribute value. --- libopenage/gamestate/component/api/live.cpp | 14 +++++++++++++- libopenage/gamestate/component/api/live.h | 13 ++++++++++++- libopenage/gamestate/system/apply_effect.cpp | 16 +++++++++++++--- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/libopenage/gamestate/component/api/live.cpp b/libopenage/gamestate/component/api/live.cpp index 8b6ccbe6ec..e9194343ec 100644 --- a/libopenage/gamestate/component/api/live.cpp +++ b/libopenage/gamestate/component/api/live.cpp @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #include "live.h" @@ -20,6 +20,18 @@ void Live::add_attribute(const time::time_t &time, this->attributes.insert(time, attribute, starting_values); } +const attribute_value_t Live::get_attribute(const time::time_t &time, + const nyan::fqon_t &attribute) const { + auto attribute_iterator = this->attributes.at(time, attribute); + if (attribute_iterator) { + auto attribute_curve = **attribute_iterator; + return attribute_curve->get(time); + } + else { + throw Error(MSG(err) << "Attribute not found: " << attribute); + } +} + void Live::set_attribute(const time::time_t &time, const nyan::fqon_t &attribute, attribute_value_t value) { diff --git a/libopenage/gamestate/component/api/live.h b/libopenage/gamestate/component/api/live.h index 96f474a818..601afe4578 100644 --- a/libopenage/gamestate/component/api/live.h +++ b/libopenage/gamestate/component/api/live.h @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -38,6 +38,17 @@ class Live final : public APIComponent { const nyan::fqon_t &attribute, std::shared_ptr> starting_values); + /** + * Get the value of an attribute at a given time. + * + * @param time The time at which the attribute is queried. + * @param attribute Attribute identifier (fqon of the nyan object). + * + * @return Value of the attribute at the given time. + */ + const attribute_value_t get_attribute(const time::time_t &time, + const nyan::fqon_t &attribute) const; + /** * Set the value of an attribute at a given time. * diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index f0f30b6e23..52e7a8557a 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "apply_effect.h" @@ -91,18 +91,28 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptrset_init_time(start_time + delay); effects_component->set_last_used(start_time + total_time); - // Apply the effect to the live component - live_component->set_attribute(start_time + delay, attribute.get_name(), applied_value); + // Calculate the new attribute value + auto current_value = live_component->get_attribute(start_time, attribute.get_name()); + auto new_value = current_value + applied_value; + + // Apply the attribute change to the live component + live_component->set_attribute(start_time + delay, attribute.get_name(), new_value); } break; default: throw Error(MSG(err) << "Effect type not implemented: " << static_cast(effect_type)); From fc595b85d202f9c32102082501a1869aa3fdd6a5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 01:30:04 +0200 Subject: [PATCH 045/118] gamestate: Fix missing convert effect type. --- libopenage/gamestate/api/definitions.h | 2 ++ libopenage/gamestate/api/types.h | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index aa129db6be..faf114f0ea 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -147,6 +147,8 @@ static const auto EFFECT_TYPE_LOOKUP = datastructure::create_const_map Date: Thu, 10 Apr 2025 01:31:58 +0200 Subject: [PATCH 046/118] convert: Fix generated path of shared media files in modpack. --- openage/convert/entity_object/conversion/combined_sound.py | 4 ++-- openage/convert/entity_object/conversion/combined_sprite.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openage/convert/entity_object/conversion/combined_sound.py b/openage/convert/entity_object/conversion/combined_sound.py index 027df77760..6f49fb8b74 100644 --- a/openage/convert/entity_object/conversion/combined_sound.py +++ b/openage/convert/entity_object/conversion/combined_sound.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ References a sound in the game that has to be converted. @@ -86,7 +86,7 @@ def get_relative_file_location(self) -> str: is expected to be in the modpack. """ if len(self._refs) > 1: - return f"../shared/sounds/{self.filename}.opus" + return f"../../shared/sounds/{self.filename}.opus" if len(self._refs) == 1: return f"./sounds/{self.filename}.opus" diff --git a/openage/convert/entity_object/conversion/combined_sprite.py b/openage/convert/entity_object/conversion/combined_sprite.py index 798c86796d..ad1a3eb3e5 100644 --- a/openage/convert/entity_object/conversion/combined_sprite.py +++ b/openage/convert/entity_object/conversion/combined_sprite.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ References a graphic in the game that has to be converted. @@ -94,7 +94,7 @@ def get_relative_sprite_location(self) -> str: is expected to be in the modpack. """ if len(self._refs) > 1: - return f"../shared/graphics/{self.filename}.sprite" + return f"../../shared/graphics/{self.filename}.sprite" if len(self._refs) == 1: return f"./graphics/{self.filename}.sprite" From dc3d61d5ac4aaa77ec0b9855c62c05da95289d3c Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 02:02:26 +0200 Subject: [PATCH 047/118] gamestate: Only add apply effect command to game entities with matching component. --- libopenage/gamestate/event/spawn_entity.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libopenage/gamestate/event/spawn_entity.cpp b/libopenage/gamestate/event/spawn_entity.cpp index 4e665edaef..216b1fc6b9 100644 --- a/libopenage/gamestate/event/spawn_entity.cpp +++ b/libopenage/gamestate/event/spawn_entity.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "spawn_entity.h" @@ -209,10 +209,12 @@ void SpawnEntityHandler::invoke(openage::event::EventLoop & /* loop */, // 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); + if (entity->has_component(component::component_t::APPLY_EFFECT)) { + 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)); From b4e2cdb670dc68d8857f2d5264a41c5bd7f21905 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 02:03:20 +0200 Subject: [PATCH 048/118] curve: Fix wrong assertion for interpolation. --- libopenage/curve/interpolated.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libopenage/curve/interpolated.h b/libopenage/curve/interpolated.h index 89a0d6b2c9..06e45e9936 100644 --- a/libopenage/curve/interpolated.h +++ b/libopenage/curve/interpolated.h @@ -1,4 +1,4 @@ -// Copyright 2019-2024 the openage authors. See copying.md for legal info. +// Copyright 2019-2025 the openage authors. See copying.md for legal info. #pragma once @@ -47,13 +47,14 @@ class Interpolated : public BaseCurve { * * @param before Index of the earlier keyframe. * @param after Index of the later keyframe. - * @param elapsed Elapsed time after the earlier keyframe. + * @param elapsed_frac Fraction of elapsed time between \p before and \p after. + * Must be between 0.0 and 1.0. * * @return Interpolated value. */ T interpolate(typename KeyframeContainer::elem_ptr before, typename KeyframeContainer::elem_ptr after, - double elapsed) const; + double elapsed_frac) const; }; @@ -84,7 +85,7 @@ T Interpolated::get(const time::time_t &time) const { return this->container.get(e).val(); } else { - // Interpolation between time(now) and time(next) that has elapsed + // Interpolation between time(now) and time(next) that has elapsed_frac // TODO: Elapsed time does not use fixed point arithmetic double elapsed_frac = offset.to_double() / interval.to_double(); @@ -133,15 +134,14 @@ void Interpolated::compress(const time::time_t &start) { template inline T Interpolated::interpolate(typename KeyframeContainer::elem_ptr before, typename KeyframeContainer::elem_ptr after, - double elapsed) const { + double elapsed_frac) 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"); + ENSURE(elapsed_frac >= 0.0 && elapsed_frac <= 1.0, + "Elapsed fraction must be between 0.0 and 1.0"); // 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; + auto diff_value = (this->container.get(after).val() - this->container.get(before).val()) * elapsed_frac; return this->container.get(before).val() + diff_value; } From a95396d3ed194be86658b2f25706a39df6cae50e Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 04:55:09 +0200 Subject: [PATCH 049/118] util: Get absolute difference between two fixed point values. --- libopenage/util/fixed_point.h | 39 ++++++++++++++++++++++++++++ libopenage/util/fixed_point_test.cpp | 24 +++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index c751238cdf..d4ede98ed2 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -422,6 +422,45 @@ class FixedPoint { return *this; } + /** + * Get the absolute difference to another FixedPoint number. + * + * @param other Number to compare with. + * + * @return Absolute difference between \p this and \p other. + */ + constexpr same_type_but_unsigned abs_diff(const FixedPoint &other) const { + return FixedPoint::abs_diff(*this, other); + } + + /** + * Get the absolute difference between two FixedPoint numbers. + * + * Safe for signed types against integer overflow. + * + * @param first First number to compare with. + * @param second Second number to compare with. + * + * @return Absolute difference between \p first and \p second. + */ + static constexpr same_type_but_unsigned abs_diff(const FixedPoint &first, const FixedPoint &second) { + int_type diff; + int_type max = std::max(first.raw_value, second.raw_value); + int_type min = std::min(first.raw_value, second.raw_value); + + diff = max - min; + + // check if there was an overflow + if (diff < 0) { + // if there is an overflow, max is positive and min is negative + // we can safely cast max to unsigned and subtract min + unsigned_int_type u_diff = static_cast(max) - min; + return FixedPoint::same_type_but_unsigned::from_raw_value(u_diff); + } + + return FixedPoint::same_type_but_unsigned::from_raw_value(diff); + } + void swap(FixedPoint &rhs) { std::swap(this->raw_value, rhs.raw_value); } diff --git a/libopenage/util/fixed_point_test.cpp b/libopenage/util/fixed_point_test.cpp index b3690b8d15..ef1740c9af 100644 --- a/libopenage/util/fixed_point_test.cpp +++ b/libopenage/util/fixed_point_test.cpp @@ -83,6 +83,30 @@ void fixed_point() { TESTEQUALS(e < f, false); TESTEQUALS(e > f, true); + // test absolute difference + FixedPoint g(1.5); + FixedPoint h(2.5); + FixedPoint i = FixedPoint::min_value(); // -8.0 + FixedPoint j = FixedPoint::max_value(); // 7.9375 + + FixedPoint k = FixedPoint::abs_diff(g, h); + TESTEQUALS(k, 1); + k = FixedPoint::abs_diff(h, g); + TESTEQUALS(k, 1); + k = FixedPoint::abs_diff(g, i); + TESTEQUALS(k, 9.5); + k = FixedPoint::abs_diff(i, g); + TESTEQUALS(k, 9.5); + k = h.abs_diff(j); + TESTEQUALS(k, 5.4375); + k = j.abs_diff(h); + TESTEQUALS(k, 5.4375); + k = i.abs_diff(j); + FixedPoint max = FixedPoint::max_value(); + TESTEQUALS(k, max); + k = j.abs_diff(i); + TESTEQUALS(k, max); + // test the string I/O functions FString s; s << a; From c54b66ef87bd5075e51e2021f7f5ba6d549628d6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 04:55:54 +0200 Subject: [PATCH 050/118] curve: Fix overflow for interpolation time offsets. --- libopenage/curve/interpolated.h | 19 ++++++++++--------- libopenage/time/time.h | 8 +++++++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libopenage/curve/interpolated.h b/libopenage/curve/interpolated.h index 06e45e9936..6a4504527c 100644 --- a/libopenage/curve/interpolated.h +++ b/libopenage/curve/interpolated.h @@ -66,17 +66,15 @@ T Interpolated::get(const time::time_t &time) const { auto nxt = e; ++nxt; - time::time_t interval = 0; - - auto offset = time - this->container.get(e).time(); + // difference between time and previous keyframe + auto offset = time.abs_diff(this->container.get(e).time()); + // difference between previous keyframe and next keyframe + time::time_duration_t interval = 0; if (nxt != this->container.size()) { - interval = this->container.get(nxt).time() - this->container.get(e).time(); + interval = this->container.get(nxt).time().abs_diff(this->container.get(e).time()); } - // here, offset > interval will never hold. - // otherwise the underlying storage is broken. - // 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 @@ -85,6 +83,9 @@ T Interpolated::get(const time::time_t &time) const { return this->container.get(e).val(); } else { + // here, offset > interval will never hold. + // otherwise the underlying storage is broken. + // Interpolation between time(now) and time(next) that has elapsed_frac // TODO: Elapsed time does not use fixed point arithmetic double elapsed_frac = offset.to_double() / interval.to_double(); @@ -103,8 +104,8 @@ void Interpolated::compress(const time::time_t &start) { auto last_kept = e; for (auto current = e + 1; current < this->container.size() - 1; ++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 offset = this->container.get(current).time().abs_diff(this->container.get(last_kept).time()); + auto interval = this->container.get(current + 1).time().abs_diff(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) diff --git a/libopenage/time/time.h b/libopenage/time/time.h index ceac0c6365..914854685e 100644 --- a/libopenage/time/time.h +++ b/libopenage/time/time.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -15,6 +15,12 @@ namespace openage::time { */ using time_t = util::FixedPoint; +/** + * Defines the type that is used for time durations. + * Same as time_t, but unsigned to cover the whole range of time_t. + */ +using time_duration_t = util::FixedPoint; + /** * Minimum time value. */ From 81db1fea14b7786a7919a380eabad5058f58831f Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 11 Apr 2025 20:58:34 +0200 Subject: [PATCH 051/118] util: Add concepts for fixed point types. --- libopenage/util/fixed_point.h | 51 ++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index d4ede98ed2..1c25457acb 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -18,13 +18,26 @@ namespace openage { namespace util { +/** + * Concept for fixed point storage types. + */ +template +concept StorageLike = std::is_integral::value; + +/** + * Concept for fixed point intermediate types. + */ +template +concept IntermediateLike = StorageLike; + + /** * Helper function that performs a left shift without causing undefined * behavior. * regular left-shift is undefined if amount >= bitwidth, * or amount >= bitwidth - 1 for signed integers. */ -template +template constexpr static typename std::enable_if<(amount + (std::is_signed::value ? 1 : 0) < sizeof(T) * CHAR_BIT), T>::type safe_shiftleft(T value) { @@ -38,14 +51,14 @@ constexpr static * behavior. * right-shift is usually undefined if amount >= bit size. */ -template +template constexpr static typename std::enable_if<(amount >= sizeof(T) * CHAR_BIT), T>::type safe_shiftright(T value) { return value < 0 ? -1 : 0; } -template +template constexpr static typename std::enable_if<(amount < sizeof(T) * CHAR_BIT), T>::type safe_shiftright(T value) { @@ -57,7 +70,7 @@ constexpr static * Helper function that performs either a safe shift-right (amount < 0), * or a safe shift-left (amount >= 0). */ -template +template constexpr static typename std::enable_if<(amount < 0), T>::type safe_shift(T value) { @@ -65,7 +78,7 @@ constexpr static } -template +template constexpr static typename std::enable_if<(amount >= 0), T>::type safe_shift(T value) { @@ -86,7 +99,7 @@ constexpr static * If you change this class, remember to update the gdb pretty printers * in etc/gdb_pretty/printers.py. */ -template +template class FixedPoint { public: using raw_type = int_type; @@ -267,13 +280,13 @@ class FixedPoint { /** * Factory function to get a fixed-point number from a fixed-point number of different type. */ - template other_fractional_bits)>::type * = nullptr> + template other_fractional_bits)>::type * = nullptr> static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { return FixedPoint::from_raw_value( safe_shift(static_cast(other.get_raw_value()))); } - template ::type * = nullptr> + template ::type * = nullptr> static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { return FixedPoint::from_raw_value( static_cast(other.get_raw_value() / safe_shiftleft(1))); @@ -383,12 +396,12 @@ class FixedPoint { return FixedPoint::this_type::from_raw_value(-this->raw_value); } - template + template constexpr double hypot(const FixedPoint rhs) { return std::hypot(this->to_double(), rhs.to_double()); } - template + template constexpr FixedPoint hypotfp(const FixedPoint rhs) { return FixedPoint(this->hypot(rhs)); } @@ -557,7 +570,7 @@ class FixedPoint { /** * FixedPoint + FixedPoint */ -template +template constexpr FixedPoint operator+(const FixedPoint &lhs, const FixedPoint &rhs) { return FixedPoint::from_raw_value(lhs.get_raw_value() + rhs.get_raw_value()); } @@ -565,7 +578,7 @@ constexpr FixedPoint operator+(const FixedPoint &lhs, /** * FixedPoint + double */ -template +template constexpr FixedPoint operator+(const FixedPoint &lhs, const double &rhs) { return FixedPoint{lhs} + FixedPoint::from_double(rhs); } @@ -573,7 +586,7 @@ constexpr FixedPoint operator+(const FixedPoint &lhs, /** * FixedPoint - FixedPoint */ -template +template constexpr FixedPoint operator-(const FixedPoint &lhs, const FixedPoint &rhs) { return FixedPoint::from_raw_value(lhs.get_raw_value() - rhs.get_raw_value()); } @@ -581,7 +594,7 @@ constexpr FixedPoint operator-(const FixedPoint &lhs, /** * FixedPoint - double */ -template +template constexpr FixedPoint operator-(const FixedPoint &lhs, const double &rhs) { return FixedPoint{lhs} - FixedPoint::from_double(rhs); } @@ -590,7 +603,7 @@ constexpr FixedPoint operator-(const FixedPoint &lhs, /** * FixedPoint * N */ -template +template typename std::enable_if::value, FixedPoint>::type constexpr operator*(const FixedPoint lhs, const N &rhs) { return FixedPoint::from_raw_value(lhs.get_raw_value() * rhs); } @@ -612,7 +625,7 @@ typename std::enable_if::value, FixedPoint>:: * * Use a larger intermediate type to prevent overflow */ -template +template constexpr FixedPoint operator*(const FixedPoint lhs, const FixedPoint rhs) { Inter ret = static_cast(lhs.get_raw_value()) * static_cast(rhs.get_raw_value()); ret >>= F; @@ -624,7 +637,7 @@ constexpr FixedPoint operator*(const FixedPoint lhs, c /** * FixedPoint / FixedPoint */ -template +template constexpr FixedPoint operator/(const FixedPoint lhs, const FixedPoint rhs) { Inter ret = div((static_cast(lhs.get_raw_value()) << F), static_cast(rhs.get_raw_value())); return FixedPoint::from_raw_value(static_cast(ret)); @@ -634,7 +647,7 @@ constexpr FixedPoint operator/(const FixedPoint lhs, c /** * FixedPoint / N */ -template +template constexpr FixedPoint operator/(const FixedPoint lhs, const N &rhs) { return FixedPoint::from_raw_value(div(lhs.get_raw_value(), static_cast(rhs))); } @@ -642,7 +655,7 @@ constexpr FixedPoint operator/(const FixedPoint lhs, c /** * FixedPoint % FixedPoint (modulo) */ -template +template constexpr FixedPoint operator%(const FixedPoint lhs, const FixedPoint rhs) { auto div = (lhs / rhs); auto n = div.to_int(); From 1d9b6558268cd5b92acad528157167f6aabe930d Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 11 Apr 2025 21:59:12 +0200 Subject: [PATCH 052/118] curve: Cleanup docstrings in KeyframeContainer class. --- libopenage/curve/keyframe_container.h | 205 +++++++++++++------------- 1 file changed, 103 insertions(+), 102 deletions(-) diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index a69a2c9616..fc909071e1 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -16,40 +16,38 @@ namespace openage::curve { /** - * A timely ordered list with several management functions + * A container storing time-value (keyframe) pairs ordered by time. * - * This class manages different time-based management functions for list - * approach that lies underneath. It contains list to be accessed via a - * non-accurate timing functionality, this means, that for getting a value, not - * the exact timestamp has to be known, it will always return the one closest, - * less or equal to the requested one. + * This class has several management functions for modifying and accessing the + * underlying storage. For getting a keyframe value, the exact timestamp does not + * have to be known, it will always return the one closest, less or equal to the + * requested one. **/ template class KeyframeContainer { public: /** - * A element of the curvecontainer. This is especially used to keep track of - * the value-timing. + * Element of the container. Represents a single time-value pair. */ using keyframe_t = Keyframe; /** - * The underlaying container type. + * Underlaying container type. */ using container_t = std::vector; /** - * The index type to access elements in the container + * Index type to access elements in the container */ using elem_ptr = typename container_t::size_type; /** - * The iterator type to access elements in the container + * Iterator type to access elements in the container. */ using iterator = typename container_t::const_iterator; /** - * Create a new container. + * Create a new keyframe container. * * Inserts a default element with value \p T() at \p time = -INF to ensure * that accessing the container always returns an element. @@ -59,9 +57,9 @@ class KeyframeContainer { KeyframeContainer(); /** - * Create a new container. + * Create a new keyframe container. * - * Inserts a default element at \p time = -INF to ensure + * Inserts a default element \p defaultval at \p time = -INF to ensure * that accessing the container always returns an element. * * @param defaultval Value of default element at -INF. @@ -71,63 +69,89 @@ class KeyframeContainer { KeyframeContainer(const T &defaultval); /** - * Return the number of elements in this container. - * One element is always added at -Inf by default, - * so this is usually your_added_elements + 1. + * Get the number of elements in this container. */ size_t size() const; + /** + * Get the value of the keyframe at the given index. + * + * @param idx Index of the keyframe to get. + * + * @return The keyframe at the given index. + */ const keyframe_t &get(const elem_ptr &idx) const { return this->container.at(idx); } /** - * Get the last element in the curve which is at or before the given time. - * (i.e. elem->time <= time). Given a hint where to start the search. + * Get the last element in the container which is at or before the given time. + * (i.e. elem->time <= time). Include a hint where to start the search. + * + * @param time Request time. + * @param hint Index of the approximate element location. + * + * @return Last element with time <= time. */ elem_ptr last(const time::time_t &time, const elem_ptr &hint) const; /** - * Get the last element with elem->time <= time, without a hint where to start - * searching. + * Get the last element in the container which is at or before the given time. + * (i.e. elem->time <= time). * * The usage of this method is discouraged - except if there is absolutely * no chance for you to have a hint (or the container is known to be nearly - * empty) + * empty). + * + * @param time Request time. + * + * @return Last element with time <= time. */ elem_ptr last(const time::time_t &time) const { return this->last(time, this->container.size()); } /** - * Get the last element in the curve which is before the given time. - * (i.e. elem->time < time). Given a hint where to start the search. + * Get the last element in the container which is before the given time. + * (i.e. elem->time < time). Include a hint where to start the search. + * + * @param time Request time. + * @param hint Index of the approximate element location. + * + * @return Last element with time < time. */ elem_ptr last_before(const time::time_t &time, const elem_ptr &hint) const; /** - * Get the last element with elem->time < time, without a hint where to start - * searching. + * Get the last element in the container which is before the given time. + * + * The usage of this method is discouraged - except if there is absolutely + * no chance for you to have a hint (or the container is known to be nearly + * empty). + * + * @param time Request time. + * + * @return Last element with time < time. */ elem_ptr last_before(const time::time_t &time) const { return this->last_before(time, this->container.size()); } /** - * Insert a new element without submitting a hint. The search is + * Insert a new element. The search for the insertion point is * started from the end of the data. * - * The use of this function is discouraged, use it only, if your really + * The use of this function is discouraged, use it only, if you really * do not have the possibility to get a hint. * - * If there is a keyframe with identical time, this will + * If there is already a keyframe with identical time in the container, this will * insert the new keyframe before the old one. * * @param keyframe Keyframe to insert. * - * @return The location (index) of the inserted element. + * @return Location (index) of the inserted element. */ elem_ptr insert_before(const keyframe_t &keyframe) { return this->insert_before(keyframe, this->container.size()); @@ -137,7 +161,7 @@ class KeyframeContainer { * 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 + * the inserter will start to look for an insertion point. If a good hint is * given, the runtime of this function will not be affected by the current * history size. * @@ -153,19 +177,19 @@ class KeyframeContainer { const elem_ptr &hint); /** - * Create and insert a new element without submitting a hint. The search - * is started from the end of the data. + * Create and insert a new element. The search for the insertion point is + * started from the end of the data. * - * The use of this function is discouraged, use it only, if your really + * The use of this function is discouraged, use it only, if you really * do not have the possibility to get a hint. * - * If there is a keyframe with identical time, this will + * If there is a keyframe with identical time in the container, 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. + * @return Location (index) of the inserted element. */ elem_ptr insert_before(const time::time_t &time, const T &value) { @@ -174,7 +198,12 @@ class KeyframeContainer { } /** - * Create and insert a new element. The hint gives an approximate location. + * Create and insert a new element. + * + * The hint shall give an approximate location, where + * the inserter will start to look for an 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 value with identical time, this will insert the new value * before the old one. @@ -183,7 +212,7 @@ class KeyframeContainer { * @param value Value of the new keyframe. * @param hint Index of the approximate insertion location. * - * @return The location (index) of the inserted element. + * @return Location (index) of the inserted element. */ elem_ptr insert_before(const time::time_t &time, const T &value, @@ -195,6 +224,7 @@ class KeyframeContainer { * Insert a new element, overwriting elements that have a * 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. * @@ -203,7 +233,7 @@ class KeyframeContainer { * @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. + * @return Location (index) of the inserted element. */ elem_ptr insert_overwrite(const keyframe_t &keyframe, const elem_ptr &hint, @@ -214,13 +244,13 @@ class KeyframeContainer { * 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 itonly, if your really + * The use of this function is discouraged, use it only, if you 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. + * @return 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), @@ -229,18 +259,19 @@ class KeyframeContainer { /** * Insert a new value at given time, which overwrites element(s) with - * identical time. If `overwrite_all` is false, overwrite the last same-time - * element. If `overwrite_all` is true, overwrite all elements with same-time. - * Provide a insertion hint to abbreviate the search for the + * identical time. Provide a insertion hint to abbreviate the search for the * insertion point. * + * `overwrite_all` == true -> overwrite all same-time elements. + * `overwrite_all` == false -> overwrite the last of the time-conflict elements. + * * @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. + * @return Location (index) of the inserted element. */ elem_ptr insert_overwrite(const time::time_t &time, const T &value, @@ -257,7 +288,7 @@ class KeyframeContainer { * @param keyframe Keyframe to insert. * @param hint Index of the approximate insertion location. * - * @return The location (index) of the inserted element. + * @return Location (index) of the inserted element. */ elem_ptr insert_after(const keyframe_t &keyframe, const elem_ptr &hint); @@ -267,13 +298,13 @@ class KeyframeContainer { * 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 + * The use of this function is discouraged, use it only, if you 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. + * @return Location (index) of the inserted element. */ elem_ptr insert_after(const time::time_t &time, const T &value) { @@ -289,7 +320,7 @@ class KeyframeContainer { * @param value Value of the new keyframe. * @param hint Index of the approximate insertion location. * - * @return The location (index) of the inserted element. + * @return Location (index) of the inserted element. */ elem_ptr insert_after(const time::time_t &time, const T &value, @@ -298,33 +329,41 @@ class KeyframeContainer { } /** - * Erase all elements that come after this last valid element. + * Erase all elements after the given element. + * + * @param last_valid Location of the last element to keep. + * + * @return Location (index) of the last element that was kept. */ elem_ptr erase_after(elem_ptr last_valid); /** - * Erase a single element from the curve. - * Returns the element after the deleted one. + * Erase a single element from the container. + * + * @param it Location of the element to erase. + * + * @return Location (index) of the next element after the erased one. */ elem_ptr erase(elem_ptr it); /** - * Erase all elements with given time. - * Variant without hint, starts the search at the end of the container. - * Returns the iterator after the deleted elements. + * Erase all elements with given time. Starts the search at the end of the container. + * + * @param time Time of the elements to erase. + * + * @return Location (index) of the next element after the erased one. */ elem_ptr erase(const time::time_t &time) { return this->erase(time, this->container.size()); } /** - * Erase all element with given time. - * `hint` is an iterator pointing hopefully close to the searched - * elements. + * Erase all element with given time. Include a hint where to start the search. * - * Returns the iterator after the deleted elements. - * Or, if no elements with this time exist, - * the iterator to the first element after the requested time is returned + * @param time Time of the elements to erase. + * @param hint Index of the approximate element location. + * + * @return Location (index) of the next element after the erased one. */ elem_ptr erase(const time::time_t &time, const elem_ptr &hint) { @@ -332,14 +371,14 @@ class KeyframeContainer { } /** - * Obtain an iterator to the first value with the smallest timestamp. + * Get an iterator to the first keyframe in the container. */ iterator begin() const { return this->container.begin(); } /** - * Obtain an iterator to the position after the last value. + * Get an iterator to the end of the container. */ iterator end() const { return this->container.end(); @@ -411,16 +450,12 @@ class KeyframeContainer { template KeyframeContainer::KeyframeContainer() { - // Create a default element at -Inf, that can always be dereferenced - so - // there will by definition never be a element that cannot be dereferenced this->container.push_back(keyframe_t(time::TIME_MIN, T())); } template KeyframeContainer::KeyframeContainer(const T &defaultval) { - // Create a default element at -Inf, that can always be dereferenced - so - // there will by definition never be a element that cannot be dereferenced this->container.push_back(keyframe_t(time::TIME_MIN, defaultval)); } @@ -431,14 +466,6 @@ size_t KeyframeContainer::size() const { } -/* - * Select the last element that is <= a given time. - * If there is multiple elements with the same time, return the last of them. - * If there is no element with such time, return the next element before the time. - * - * Intuitively, this function returns the element that set the last value - * that determines the curve value for a searched time. - */ template typename KeyframeContainer::elem_ptr KeyframeContainer::last(const time::time_t &time, @@ -465,16 +492,6 @@ KeyframeContainer::last(const time::time_t &time, } -/* - * Select the last element that is < a given time. - * If there is multiple elements with the same time, return the last of them. - * If there is no element with such time, return the next element before the 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 KeyframeContainer::last_before(const time::time_t &time, @@ -501,9 +518,6 @@ KeyframeContainer::last_before(const time::time_t &time, } -/* - * Determine where to insert based on time, and insert. - */ template typename KeyframeContainer::elem_ptr KeyframeContainer::insert_before(const KeyframeContainer::keyframe_t &e, @@ -527,9 +541,6 @@ KeyframeContainer::insert_before(const KeyframeContainer::keyframe_t &e, } -/* - * Determine where to insert based on time, and insert, overwriting value(s) with same time. - */ template typename KeyframeContainer::elem_ptr KeyframeContainer::insert_overwrite(const KeyframeContainer::keyframe_t &e, @@ -556,10 +567,6 @@ 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 typename KeyframeContainer::elem_ptr KeyframeContainer::insert_after(const KeyframeContainer::keyframe_t &e, @@ -576,9 +583,6 @@ 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 typename KeyframeContainer::elem_ptr KeyframeContainer::erase_after(KeyframeContainer::elem_ptr last_valid) { @@ -593,9 +597,6 @@ KeyframeContainer::erase_after(KeyframeContainer::elem_ptr last_valid) { } -/* - * Delete the element from the list and call delete on it. - */ template typename KeyframeContainer::elem_ptr KeyframeContainer::erase(KeyframeContainer::elem_ptr e) { From 5589ed38bfe48aa1a1b838bff32317be73f419f8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Apr 2025 02:00:01 +0200 Subject: [PATCH 053/118] input: Use ID texture in input game controller. --- .../input/controller/game/controller.cpp | 12 ++++-- libopenage/input/controller/game/controller.h | 37 ++++++++++++++++++- libopenage/presenter/presenter.cpp | 12 ++++-- .../renderer/stages/world/render_stage.cpp | 4 ++ .../renderer/stages/world/render_stage.h | 7 ++++ 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/libopenage/input/controller/game/controller.cpp b/libopenage/input/controller/game/controller.cpp index 96ccf53429..e36fd438a2 100644 --- a/libopenage/input/controller/game/controller.cpp +++ b/libopenage/input/controller/game/controller.cpp @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #include "controller.h" @@ -92,6 +92,10 @@ bool Controller::process(const event_arguments &ev_args, const std::shared_ptr &id_texture) { + this->id_texture = id_texture; +} + void Controller::set_drag_select_start(const coord::input &start) { std::unique_lock lock{this->mutex}; @@ -146,8 +150,8 @@ void setup_defaults(const std::shared_ptr &ctx, ctx->bind(ev_mouse_lmb_ctrl, create_entity_action); - binding_func_t move_entity{[&](const event_arguments &args, - const std::shared_ptr controller) { + binding_func_t interact_entity{[&](const event_arguments &args, + const std::shared_ptr controller) { auto mouse_pos = args.mouse.to_phys3(camera); event::EventHandler::param_map::map_t params{ {"type", gamestate::component::command::command_t::MOVE}, @@ -164,7 +168,7 @@ void setup_defaults(const std::shared_ptr &ctx, return event; }}; - binding_action move_entity_action{forward_action_t::SEND, move_entity}; + binding_action move_entity_action{forward_action_t::SEND, interact_entity}; Event ev_mouse_rmb{ event_class::MOUSE_BUTTON, Qt::MouseButton::RightButton, diff --git a/libopenage/input/controller/game/controller.h b/libopenage/input/controller/game/controller.h index a9d690f622..ddab1101af 100644 --- a/libopenage/input/controller/game/controller.h +++ b/libopenage/input/controller/game/controller.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -15,6 +15,10 @@ namespace openage { +namespace renderer { +class Texture2d; +} // namespace renderer + namespace gamestate { class GameSimulation; } @@ -36,6 +40,12 @@ class BindingContext; */ class Controller : public std::enable_shared_from_this { public: + /** + * Create a new game controller. + * + * @param controlled_factions Factions that can be managed by the controller. + * @param active_faction_id Current active faction ID. + */ Controller(const std::unordered_set &controlled_factions, size_t active_faction_id); @@ -80,6 +90,26 @@ class Controller : public std::enable_shared_from_this { */ bool process(const event_arguments &ev_args, const std::shared_ptr &ctx); + /** + * Get the texture that maps pixels to entity IDs. + * + * Each pixel value in the texture corresponds to an entity ID. This + * mapping may be used for interacting with entities in the game world. + * + * @return ID texture. + */ + const std::shared_ptr &get_id_texture() const; + + /** + * Set the texture that maps pixels to entity IDs. + * + * Each pixel value in the texture corresponds to an entity ID. This + * mapping may be used for interacting with entities in the game world. + * + * @param id_texture ID texture. + */ + void set_id_texture(const std::shared_ptr &id_texture); + /** * Set the start position of a drag selection. * @@ -120,6 +150,11 @@ class Controller : public std::enable_shared_from_this { */ std::vector> outqueue; + /** + * ID texture for interacting with game entities. + */ + std::shared_ptr id_texture; + /** * Start position of a drag selection. * diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index 9775b62d9b..baecb2ee92 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2024 the openage authors. See copying.md for legal info. +// Copyright 2019-2025 the openage authors. See copying.md for legal info. #include "presenter.h" @@ -243,13 +243,19 @@ void Presenter::init_input() { if (this->simulation) { log::log(INFO << "Loading game simulation controls"); - // TODO: Remove hardcoding + // TODO: Remove hardcoding for controlled/active factions + std::unordered_set controlled_factions{0, 1, 2, 3}; + size_t active_faction_id = 0; auto game_controller = std::make_shared( - std::unordered_set{0, 1, 2, 3}, 0); + controlled_factions, active_faction_id); + auto engine_context = std::make_shared(); input::game::setup_defaults(engine_context, this->time_loop, this->simulation, this->camera); this->input_manager->set_game_controller(game_controller); input_ctx->set_game_bindings(engine_context); + + auto id_texture = this->world_renderer->get_id_texture(); + game_controller->set_id_texture(id_texture); } // attach GUI if it's initialized diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index eec13b602b..8212907daf 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -117,6 +117,10 @@ void WorldRenderStage::resize(size_t width, size_t height) { this->render_pass->set_target(fbo); } +const std::shared_ptr &WorldRenderStage::get_id_texture() const { + return this->id_texture; +} + void WorldRenderStage::initialize_render_pass(size_t width, size_t height, const util::Path &shaderdir) { diff --git a/libopenage/renderer/stages/world/render_stage.h b/libopenage/renderer/stages/world/render_stage.h index 7a0fe02a4c..bdd2a71d29 100644 --- a/libopenage/renderer/stages/world/render_stage.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -90,6 +90,13 @@ class WorldRenderStage { */ void resize(size_t width, size_t height); + /** + * Get the ID texture of the world renderer. + * + * @return ID texture. + */ + const std::shared_ptr &get_id_texture() const; + private: /** * Create the render pass for world drawing. From aa2870f98e7617a6a548edd46ee9bf4d1f77f392 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Apr 2025 02:30:00 +0200 Subject: [PATCH 054/118] input: Send ApplyEffect command to simulation. --- libopenage/gamestate/event/send_command.cpp | 5 ++- libopenage/gamestate/event/spawn_entity.cpp | 8 ++--- .../input/controller/game/controller.cpp | 33 +++++++++++++++---- libopenage/input/input_manager.cpp | 6 +++- libopenage/input/input_manager.h | 22 +++++++++++-- libopenage/presenter/presenter.cpp | 9 ++++- 6 files changed, 67 insertions(+), 16 deletions(-) diff --git a/libopenage/gamestate/event/send_command.cpp b/libopenage/gamestate/event/send_command.cpp index e01e7a5e77..bf785950b0 100644 --- a/libopenage/gamestate/event/send_command.cpp +++ b/libopenage/gamestate/event/send_command.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "send_command.h" @@ -74,6 +74,9 @@ void SendCommandHandler::invoke(openage::event::EventLoop & /* loop */, params.get("target", coord::phys3{0, 0, 0}))); break; + case component::command::command_t::APPLY_EFFECT: + // TODO: add command + break; default: break; } diff --git a/libopenage/gamestate/event/spawn_entity.cpp b/libopenage/gamestate/event/spawn_entity.cpp index 216b1fc6b9..736946bdb7 100644 --- a/libopenage/gamestate/event/spawn_entity.cpp +++ b/libopenage/gamestate/event/spawn_entity.cpp @@ -210,10 +210,10 @@ void SpawnEntityHandler::invoke(openage::event::EventLoop & /* loop */, // ASDF: Remove demo code below for applying effects // add apply effect command to the command queue if (entity->has_component(component::component_t::APPLY_EFFECT)) { - 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 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( diff --git a/libopenage/input/controller/game/controller.cpp b/libopenage/input/controller/game/controller.cpp index e36fd438a2..f23d268397 100644 --- a/libopenage/input/controller/game/controller.cpp +++ b/libopenage/input/controller/game/controller.cpp @@ -14,6 +14,7 @@ #include "gamestate/game_state.h" #include "gamestate/simulation.h" #include "input/controller/game/binding_context.h" +#include "renderer/texture.h" #include "time/clock.h" #include "time/time_loop.h" @@ -92,6 +93,10 @@ bool Controller::process(const event_arguments &ev_args, const std::shared_ptr &Controller::get_id_texture() const { + return this->id_texture; +} + void Controller::set_id_texture(const std::shared_ptr &id_texture) { this->id_texture = id_texture; } @@ -152,12 +157,28 @@ void setup_defaults(const std::shared_ptr &ctx, binding_func_t interact_entity{[&](const event_arguments &args, const std::shared_ptr controller) { - auto mouse_pos = args.mouse.to_phys3(camera); - event::EventHandler::param_map::map_t params{ - {"type", gamestate::component::command::command_t::MOVE}, - {"target", mouse_pos}, - {"entity_ids", controller->get_selected()}, - }; + auto id_texture = controller->get_id_texture(); + auto texture_data = id_texture->into_data(); + + event::EventHandler::param_map::map_t params{}; + + auto target_entity_id = texture_data.read_pixel(args.mouse.x, args.mouse.y); + log::log(DBG << "Targeting entity ID: " << target_entity_id); + if (target_entity_id == 0) { + auto mouse_pos = args.mouse.to_phys3(camera); + params = { + {"type", gamestate::component::command::command_t::MOVE}, + {"target", mouse_pos}, + {"entity_ids", controller->get_selected()}, + }; + } + else { + params = { + {"type", gamestate::component::command::command_t::APPLY_EFFECT}, + {"target", target_entity_id}, + {"entity_ids", controller->get_selected()}, + }; + } auto event = simulation->get_event_loop()->create_event( "game.send_command", diff --git a/libopenage/input/input_manager.cpp b/libopenage/input/input_manager.cpp index 4857863eb1..b3198fd520 100644 --- a/libopenage/input/input_manager.cpp +++ b/libopenage/input/input_manager.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "input_manager.h" @@ -155,6 +155,10 @@ bool InputManager::process(const QEvent &ev) { return false; } +void InputManager::set_id_texture(const std::shared_ptr &id_texture) { + this->game_controller->set_id_texture(id_texture); +} + void InputManager::process_action(const input::Event &ev, const input_action &action, const std::shared_ptr &ctx) { diff --git a/libopenage/input/input_manager.h b/libopenage/input/input_manager.h index 25b6315341..c28372a198 100644 --- a/libopenage/input/input_manager.h +++ b/libopenage/input/input_manager.h @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #pragma once @@ -16,7 +16,13 @@ namespace qtgui { class GuiInput; } -namespace openage::input { +namespace openage { + +namespace renderer { +class Texture2d; +} // namespace renderer + +namespace input { namespace camera { class Controller; @@ -149,6 +155,15 @@ class InputManager { */ bool process(const QEvent &ev); + /** + * Set the texture that maps pixels to entity IDs. + * + * Each pixel value in the texture corresponds to an entity ID. This + * mapping may be used for interacting with entities in the game world. + * + * @param id_texture ID texture. + */ + void set_id_texture(const std::shared_ptr &id_texture); private: /** @@ -222,4 +237,5 @@ class InputManager { */ void setup_defaults(const std::shared_ptr &ctx); -} // namespace openage::input +} // namespace input +} // namespace openage diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index baecb2ee92..118b2f0c2d 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -255,7 +255,14 @@ void Presenter::init_input() { input_ctx->set_game_bindings(engine_context); auto id_texture = this->world_renderer->get_id_texture(); - game_controller->set_id_texture(id_texture); + this->input_manager->set_id_texture(id_texture); + + window->add_resize_callback([&](size_t /* width */, size_t /* height */, double /*scale*/) { + // TODO: We must guarantee that this happens AFTER the world renderer + // has resized its textures. + auto id_texture = this->world_renderer->get_id_texture(); + this->input_manager->set_id_texture(id_texture); + }); } // attach GUI if it's initialized From 21158cc6d336a95ba425f60271792a237a61f222 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Apr 2025 03:28:52 +0200 Subject: [PATCH 055/118] util: Fix writeability check for Directory class. --- libopenage/util/fslike/directory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/util/fslike/directory.cpp b/libopenage/util/fslike/directory.cpp index 847ffe4d80..7432594b09 100644 --- a/libopenage/util/fslike/directory.cpp +++ b/libopenage/util/fslike/directory.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "directory.h" @@ -110,7 +110,7 @@ bool Directory::writable(const Path::parts_t &parts) { } const std::string path = this->resolve(parts_test); - return access(path.c_str(), W_OK); + return access(path.c_str(), W_OK) == 0; } From 4c6bd6aeaca7735da4e883fc5e374113beb859b0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 18:42:47 +0200 Subject: [PATCH 056/118] renderer: Update docstrings. --- libopenage/renderer/opengl/renderer.h | 122 +++++++++++++++++-- libopenage/renderer/resources/texture_data.h | 82 +++++++++---- 2 files changed, 171 insertions(+), 33 deletions(-) diff --git a/libopenage/renderer/opengl/renderer.h b/libopenage/renderer/opengl/renderer.h index 751af7cacc..5d26d5cb87 100644 --- a/libopenage/renderer/opengl/renderer.h +++ b/libopenage/renderer/opengl/renderer.h @@ -33,31 +33,135 @@ class GlRenderer final : public Renderer { GlRenderer(const std::shared_ptr &ctx, const util::Vector2s &viewport_size); - std::shared_ptr add_texture(resources::Texture2dData const &) override; - std::shared_ptr add_texture(resources::Texture2dInfo const &) override; + /** + * Add a new texture from existing pixel data. + * + * @param data Texture data to upload to the GPU. + * + * @return Created texture object. + */ + std::shared_ptr add_texture(resources::Texture2dData const &data) override; - std::shared_ptr add_shader(std::vector const &) override; + /** + * Add a new texture from a texture information object. + * + * @param info Texture information describing texture size and format. + * + * @return Created texture object. + */ + std::shared_ptr add_texture(resources::Texture2dInfo const &info) override; - std::shared_ptr add_mesh_geometry(resources::MeshData const &) override; + /** + * Add a new shader program from shader source code. + * + * @param srcs Shader source codes to compile into a shader program. + * + * @return Created shader program. + */ + std::shared_ptr add_shader(std::vector const &srcs) override; + + /** + * Add a new geometry object from existing mesh data. + * + * Used for complex geometry with vertex attributes. + * + * @param mesh Mesh data to upload to the GPU. + * + * @return Created geometry object. + */ + std::shared_ptr add_mesh_geometry(resources::MeshData const &mesh) override; + + /** + * Add a new geometry object using a bufferless quad. + * + * Used for drawing a simple quad (rectangle). + * + * @return Created geometry object. + */ std::shared_ptr add_bufferless_quad() override; - std::shared_ptr add_render_pass(std::vector, const std::shared_ptr &) override; + /** + * Add a new render pass. + * + * Render passes group renderables that are drawn to the same target. + * + * @param renderables Renderables to be drawn in the pass. + * @param target Render target to draw into. + * + * @return Created render pass. + */ + std::shared_ptr add_render_pass(std::vector renderables, + const std::shared_ptr &target) override; - std::shared_ptr create_texture_target(std::vector> const &) override; + /** + * Add a render target that draws into the given texture attachments. + * + * Textures are attached in the order they appear in \p textures (for color attachments). + * Make sure to configure \p textures to match the layout of the output in the shader. + * + * @param textures Textures to attach to the framebuffer. + * + * @return Created render target. + */ + std::shared_ptr create_texture_target(std::vector> const &textures) override; + /** + * Get the render target for displaying on screen, i.e. targetting the window + * of the OpenGL context. + * + * @return Display target. + */ std::shared_ptr get_display_target() override; + /** + * Add a new uniform buffer from a uniform buffer information object. + * + * @param info Uniform buffer information describing the layout of the buffer. + * + * @return Created uniform buffer. + */ std::shared_ptr add_uniform_buffer(resources::UniformBufferInfo const &) override; - std::shared_ptr add_uniform_buffer(std::shared_ptr const &, - std::string const &) override; + /** + * Add a new uniform buffer from a shader program that has a uniform block. + * + * @param prog Shader program. The uniform block must be defined in the program. + * @param block_name Name of the block in the shader program. + * + * @return Created uniform buffer. + */ + std::shared_ptr add_uniform_buffer(std::shared_ptr const &prog, + std::string const &block_name) override; + + /** + * Get the current texture output of the display render target, i.e. the + * contents of the default framebuffer. + * + * @return Texture data from the display framebuffer. + */ resources::Texture2dData display_into_data() override; + /** + * Resize the display target to the given size. + * + * @param width New width. + * @param height New height. + */ void resize_display_target(size_t width, size_t height); + /** + * Check whether the graphics backend encountered any errors. + */ void check_error() override; - void render(const std::shared_ptr &) override; + /** + * Render the given render pass. + * + * Iterates over the renderables in the pass and draws them to the target. + * + * @param pass Render pass. + */ + void render(const std::shared_ptr &pass) override; private: /// Optimize the render pass by reordering stuff diff --git a/libopenage/renderer/resources/texture_data.h b/libopenage/renderer/resources/texture_data.h index 004a2bda1e..b6ac27acf6 100644 --- a/libopenage/renderer/resources/texture_data.h +++ b/libopenage/renderer/resources/texture_data.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -12,46 +12,72 @@ #include "texture_info.h" - namespace openage { namespace util { class Path; } namespace renderer::resources { -/// Stores 2D texture data in a CPU-accessible byte buffer. Provides methods for loading from -/// and storing onto disk, as well as sending to and receiving from graphics hardware. +/** + * Stores 2D texture data in a CPU-accessible byte buffer. Provides methods for loading from + * and storing onto disk, as well as sending to and receiving from graphics hardware. + */ class Texture2dData { public: - /// Create a texture from an image file. - /// @param path Path to the image file. - /// - /// Uses QImage internally. + /** + * Create a texture from an image file. + * + * Uses QImage internally. For supported image file types, + * see the QImage initialization in the engine. + * + * @param path Path to the image file. + */ Texture2dData(const util::Path &path); - /// Create a texture from info. - /// - /// Uses QImage internally. For supported image file types, - /// see the QImage initialization in the engine. + /** + * Create a texture from info. + * + * Uses QImage internally. For supported image file types, + * see the QImage initialization in the engine. + */ Texture2dData(Texture2dInfo const &info); - /// Construct by moving the information and raw texture data from somewhere else. + /** + * Construct by moving the information and raw texture data from somewhere else. + */ Texture2dData(Texture2dInfo const &info, std::vector &&data); - /// Flips the texture along the Y-axis and returns the flipped data with the same info. - /// Sometimes necessary when converting between storage modes. + /** + * Flips the texture along the Y-axis and returns the flipped data with the same info. + * Sometimes necessary when converting between storage modes. + */ Texture2dData flip_y(); - /// Returns the information describing this texture data. + /** + * Returns the information describing this texture data. + */ const Texture2dInfo &get_info() const; - /// Returns a pointer to the raw texture data, in row-major order. + /** + * Returns a pointer to the raw texture data, in row-major order. + */ const uint8_t *get_data() const; - /// Reads the pixel at the given position and casts it to the given type. - /// The texture is _not_ read as if it consisted of pixels of the given type, - /// but rather according to its original pixel format, so the coordinates - /// have to be specified according to that. + /** + * Reads the pixel at the given position and casts it to the given type. + * The texture is _not_ read as if it consisted of pixels of the given type, + * but rather according to its original pixel format, so the coordinates + * have to be specified according to that. + * + * @tparam T The type to cast the pixel to. + * + * @param x The x-coordinate of the pixel. + * @param y The y-coordinate of the pixel. + * + * @return The pixel value cast to the given type. + * + * @throws Error if the pixel position is outside the texture. + */ template T read_pixel(size_t x, size_t y) const { const uint8_t *data = this->data.data(); @@ -66,14 +92,22 @@ class Texture2dData { return *reinterpret_cast(data + off); } - /// Stores this texture data in the given file in the PNG format. + /** + * Stores this texture data in the given file in the PNG format. + * + * @param file The file path to store the texture data. + */ void store(const util::Path &file) const; private: - /// Information about this texture data. + /** + * Information about this texture data. + */ Texture2dInfo info; - /// The raw texture data. + /** + * The raw texture data. + */ std::vector data; }; From 98e2f3aaa7278d879b82f7b3764376328e459acd Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 18:43:47 +0200 Subject: [PATCH 057/118] renderer: Swap order of texture targets in world render stage. --- libopenage/renderer/stages/world/render_stage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index 8212907daf..082b00cad1 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -113,7 +113,7 @@ void WorldRenderStage::resize(size_t width, size_t height) { this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); this->id_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::r32ui)); - auto fbo = this->renderer->create_texture_target({this->output_texture, this->depth_texture, this->id_texture}); + auto fbo = this->renderer->create_texture_target({this->output_texture, this->id_texture, this->depth_texture}); this->render_pass->set_target(fbo); } @@ -145,7 +145,7 @@ void WorldRenderStage::initialize_render_pass(size_t width, this->display_shader = this->renderer->add_shader({vert_shader_src, frag_shader_src}); this->display_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer()); - auto fbo = this->renderer->create_texture_target({this->output_texture, this->depth_texture, this->id_texture}); + auto fbo = this->renderer->create_texture_target({this->output_texture, this->id_texture, this->depth_texture}); this->render_pass = this->renderer->add_render_pass({}, fbo); } From 52d1596d508ad274691a3dd785737e45ada6cdd5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 18:45:26 +0200 Subject: [PATCH 058/118] renderer: Add r32ui as supported output format for writing texture to file. --- libopenage/renderer/resources/texture_data.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libopenage/renderer/resources/texture_data.cpp b/libopenage/renderer/resources/texture_data.cpp index fe11c19ce5..ddee5ad62f 100644 --- a/libopenage/renderer/resources/texture_data.cpp +++ b/libopenage/renderer/resources/texture_data.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "texture_data.h" @@ -153,10 +153,6 @@ const uint8_t *Texture2dData::get_data() const { void Texture2dData::store(const util::Path &file) const { log::log(MSG(info) << "Saving texture data to " << file); - if (this->info.get_format() != pixel_format::rgba8) { - throw Error(MSG(err) << "Storing 2D textures into files is unimplemented. PRs welcome :D"); - } - auto size = this->info.get_size(); QImage::Format pix_fmt; @@ -171,8 +167,11 @@ void Texture2dData::store(const util::Path &file) const { case pixel_format::rgba8: pix_fmt = QImage::Format_RGBA8888; break; + case pixel_format::r32ui: + pix_fmt = QImage::Format_RGBA8888; + break; default: - throw Error(MSG(err) << "Texture uses an unsupported format."); + throw Error(MSG(err) << "Texture uses an unsupported format for storing. PRs welcome :D"); } QImage image{this->data.data(), size.first, size.second, pix_fmt}; From 01424b5d508ad7bc25585c3f505f7a4dc399bf1e Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 19:03:38 +0200 Subject: [PATCH 059/118] gamestate: Process ApplyEffect command from input system. --- libopenage/gamestate/event/send_command.cpp | 6 +++++- libopenage/gamestate/event/spawn_entity.cpp | 9 --------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/libopenage/gamestate/event/send_command.cpp b/libopenage/gamestate/event/send_command.cpp index bf785950b0..1a2cb20d43 100644 --- a/libopenage/gamestate/event/send_command.cpp +++ b/libopenage/gamestate/event/send_command.cpp @@ -6,6 +6,7 @@ #include "coord/phys.h" #include "gamestate/component/internal/command_queue.h" +#include "gamestate/component/internal/commands/apply_effect.h" #include "gamestate/component/internal/commands/idle.h" #include "gamestate/component/internal/commands/move.h" #include "gamestate/component/types.h" @@ -75,7 +76,10 @@ void SendCommandHandler::invoke(openage::event::EventLoop & /* loop */, coord::phys3{0, 0, 0}))); break; case component::command::command_t::APPLY_EFFECT: - // TODO: add command + command_queue->add_command( + time, + std::make_shared( + params.get("target", 0))); break; default: break; diff --git a/libopenage/gamestate/event/spawn_entity.cpp b/libopenage/gamestate/event/spawn_entity.cpp index 736946bdb7..45dbb08f64 100644 --- a/libopenage/gamestate/event/spawn_entity.cpp +++ b/libopenage/gamestate/event/spawn_entity.cpp @@ -207,15 +207,6 @@ 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 - if (entity->has_component(component::component_t::APPLY_EFFECT)) { - // 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); From a192190892cfc18d2edf0d54b89f2d94b36bcc05 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 22:01:55 +0200 Subject: [PATCH 060/118] renderer: Update docstring format for texture.h. --- libopenage/renderer/texture.h | 40 ++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/libopenage/renderer/texture.h b/libopenage/renderer/texture.h index c2abdab2d2..e993d99e26 100644 --- a/libopenage/renderer/texture.h +++ b/libopenage/renderer/texture.h @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,28 +8,48 @@ namespace openage { namespace renderer { -/// An abstract base for a handle to a texture buffer allocated in graphics hardware. -/// Can be obtained by passing texture data to the renderer. +/** + * An abstract base for a handle to a texture buffer allocated in graphics hardware. + * Can be obtained by passing texture data to the renderer. + */ class Texture2d { public: virtual ~Texture2d(); - /// Returns the texture information. + /** + * Get the texture information. + * + * @return Information about the texture, such as size and format. + */ const resources::Texture2dInfo &get_info() const; - /// Copies this texture's data from graphics hardware into a CPU-accessible - /// Texture2dData buffer. + /** + * Copies this texture's data from graphics hardware into a CPU-accessible + * Texture2dData buffer. + * + * @return A Texture2dData object containing the texture data. + */ virtual resources::Texture2dData into_data() = 0; - /// Uploads the provided data into the GPU texture storage. The format has - /// to match the format this Texture was originally created with. + /** + * Uploads the provided data into the GPU texture storage. The format has + * to match the format this Texture was originally created with. + * + * @param data The texture data to upload. + */ virtual void upload(resources::Texture2dData const &) = 0; protected: - /// Constructs the base with the given information. + /** + * Constructs the base with the given information. + * + * @param info Information about the texture, such as size and format. + */ Texture2d(const resources::Texture2dInfo &); - /// Information about the size, format, etc. of this texture. + /** + * Information about the size, format, etc. of this texture. + */ resources::Texture2dInfo info; }; From 0d7242c028d77b34f5a4dde92255fc011006de8e Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 22:45:30 +0200 Subject: [PATCH 061/118] renderer: Allow resizing texture without recreating it. --- libopenage/renderer/opengl/texture.cpp | 64 ++++++++++++++++++- libopenage/renderer/opengl/texture.h | 4 +- .../renderer/resources/texture_info.cpp | 6 +- libopenage/renderer/resources/texture_info.h | 9 ++- libopenage/renderer/texture.h | 13 ++++ 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/libopenage/renderer/opengl/texture.cpp b/libopenage/renderer/opengl/texture.cpp index 5e795bcd82..1d317af8da 100644 --- a/libopenage/renderer/opengl/texture.cpp +++ b/libopenage/renderer/opengl/texture.cpp @@ -1,9 +1,10 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "texture.h" #include +#include #include #include "../../datastructure/constexpr_map.h" @@ -86,7 +87,7 @@ GlTexture2d::GlTexture2d(const std::shared_ptr &context, std::get<2>(fmt_in_out), nullptr); - // TODO these are outdated, use sampler settings + // TODO: these are outdated, use sampler settings glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -94,6 +95,65 @@ GlTexture2d::GlTexture2d(const std::shared_ptr &context, << size.first << "x" << size.second << ")"); } +void GlTexture2d::resize(size_t width, size_t height) { + auto prev_size = this->info.get_size(); + if (width == static_cast(prev_size.first) + and height == static_cast(prev_size.second)) { + // size is the same, no need to resize + log::log(MSG(dbg) << "Texture resize called, but size is unchanged (size: " + << prev_size.first << "x" << prev_size.second << ")"); + return; + } + + // only allow resizing for internal textures that are not created from + // image files + // TODO: maybe allow this for all textures? + if (this->info.get_image_path().has_value()) { + throw Error(MSG(err) << "Cannot resize a texture that was created from an image file."); + } + if (this->info.get_subtex_count() != 0) { + throw Error(MSG(err) << "Cannot resize a texture that has subtextures."); + } + + // create new info object + this->info = resources::Texture2dInfo(width, + height, + this->info.get_format(), + this->info.get_image_path(), + this->info.get_row_alignment()); + + glBindTexture(GL_TEXTURE_2D, *this->handle); + + auto fmt_in_out = GL_PIXEL_FORMAT.get(this->info.get_format()); + auto size = this->info.get_size(); + + // redefine the texture with the new size + glTexImage2D( + GL_TEXTURE_2D, + 0, + std::get<0>(fmt_in_out), + size.first, + size.second, + 0, + std::get<1>(fmt_in_out), + std::get<2>(fmt_in_out), + nullptr); + + // copy the old texture data into the new texture + glCopyTexSubImage2D( + GL_TEXTURE_2D, + 0, + 0, + 0, + 0, + 0, + std::min(size.first, prev_size.first), // avoid buffer overread with std::min + std::min(size.second, prev_size.second)); + + log::log(MSG(dbg) << "Resized OpenGL texture (size: " + << width << "x" << height << ")"); +} + resources::Texture2dData GlTexture2d::into_data() { auto fmt_in_out = GL_PIXEL_FORMAT.get(this->info.get_format()); std::vector data(this->info.get_data_size()); diff --git a/libopenage/renderer/opengl/texture.h b/libopenage/renderer/opengl/texture.h index fbc52d4423..8767ed9860 100644 --- a/libopenage/renderer/opengl/texture.h +++ b/libopenage/renderer/opengl/texture.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #pragma once @@ -23,6 +23,8 @@ class GlTexture2d final : public Texture2d GlTexture2d(const std::shared_ptr &context, resources::Texture2dInfo const &); + void resize(size_t width, size_t height) override; + resources::Texture2dData into_data() override; void upload(resources::Texture2dData const &) override; diff --git a/libopenage/renderer/resources/texture_info.cpp b/libopenage/renderer/resources/texture_info.cpp index dfe3c573e7..e85086b721 100644 --- a/libopenage/renderer/resources/texture_info.cpp +++ b/libopenage/renderer/resources/texture_info.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "texture_info.h" @@ -71,6 +71,10 @@ size_t Texture2dInfo::get_subtex_count() const { return this->subtextures.size(); } +const std::vector &Texture2dInfo::get_subtextures() const { + return this->subtextures; +} + const Texture2dSubInfo &Texture2dInfo::get_subtex_info(size_t subidx) const { if (subidx < this->subtextures.size()) [[likely]] { return this->subtextures[subidx]; diff --git a/libopenage/renderer/resources/texture_info.h b/libopenage/renderer/resources/texture_info.h index 7884fe0f79..6b55d6f4b6 100644 --- a/libopenage/renderer/resources/texture_info.h +++ b/libopenage/renderer/resources/texture_info.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -151,6 +151,13 @@ class Texture2dInfo { */ size_t get_subtex_count() const; + /** + * Get the subtexture information for all subtextures. + * + * @return Subtexture information objects. + */ + const std::vector &get_subtextures() const; + /** * Get the subtexture information for a specific subtexture. * diff --git a/libopenage/renderer/texture.h b/libopenage/renderer/texture.h index e993d99e26..3789fb4627 100644 --- a/libopenage/renderer/texture.h +++ b/libopenage/renderer/texture.h @@ -23,6 +23,19 @@ class Texture2d { */ const resources::Texture2dInfo &get_info() const; + /** + * Resize the texture to a new size. + * + * Resizing is propagated to the GPU, so the texture may be reallocated depending on the + * underlying graphics API. The texture info is updated accordingly. + * + * Texture created from images cannot be resized. + * + * @param width New width of the texture. + * @param height New height of the texture. + */ + virtual void resize(size_t width, size_t height) = 0; + /** * Copies this texture's data from graphics hardware into a CPU-accessible * Texture2dData buffer. From 934e16f6043180815b5efc3c00299093a65fa320 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 23:29:20 +0200 Subject: [PATCH 062/118] renderer: Resize all texture targets in render stages with new resize method. --- libopenage/renderer/opengl/texture.cpp | 11 +---------- libopenage/renderer/stages/hud/render_stage.cpp | 6 +++--- libopenage/renderer/stages/skybox/render_stage.cpp | 6 ++++-- libopenage/renderer/stages/terrain/render_stage.cpp | 7 ++++--- libopenage/renderer/stages/world/render_stage.cpp | 6 +++--- 5 files changed, 15 insertions(+), 21 deletions(-) diff --git a/libopenage/renderer/opengl/texture.cpp b/libopenage/renderer/opengl/texture.cpp index 1d317af8da..6a9d8d0538 100644 --- a/libopenage/renderer/opengl/texture.cpp +++ b/libopenage/renderer/opengl/texture.cpp @@ -139,16 +139,7 @@ void GlTexture2d::resize(size_t width, size_t height) { std::get<2>(fmt_in_out), nullptr); - // copy the old texture data into the new texture - glCopyTexSubImage2D( - GL_TEXTURE_2D, - 0, - 0, - 0, - 0, - 0, - std::min(size.first, prev_size.first), // avoid buffer overread with std::min - std::min(size.second, prev_size.second)); + // TODO: copy the old texture data into the new texture log::log(MSG(dbg) << "Resized OpenGL texture (size: " << width << "x" << height << ")"); diff --git a/libopenage/renderer/stages/hud/render_stage.cpp b/libopenage/renderer/stages/hud/render_stage.cpp index d5d7c5b83f..3bfeff6d77 100644 --- a/libopenage/renderer/stages/hud/render_stage.cpp +++ b/libopenage/renderer/stages/hud/render_stage.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "render_stage.h" @@ -92,8 +92,8 @@ void HudRenderStage::update() { } void HudRenderStage::resize(size_t width, size_t height) { - this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); - this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); + this->output_texture->resize(width, height); + this->depth_texture->resize(width, height); auto fbo = this->renderer->create_texture_target({this->output_texture, this->depth_texture}); this->render_pass->set_target(fbo); diff --git a/libopenage/renderer/stages/skybox/render_stage.cpp b/libopenage/renderer/stages/skybox/render_stage.cpp index b6fe47a7db..bc0a7c2f9d 100644 --- a/libopenage/renderer/stages/skybox/render_stage.cpp +++ b/libopenage/renderer/stages/skybox/render_stage.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2024 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. #include "render_stage.h" @@ -10,10 +10,12 @@ #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" #include "renderer/shader_program.h" +#include "renderer/texture.h" #include "renderer/uniform_input.h" #include "renderer/window.h" #include "util/path.h" + namespace openage::renderer::skybox { SkyboxRenderStage::SkyboxRenderStage(const std::shared_ptr &window, @@ -67,7 +69,7 @@ void SkyboxRenderStage::set_color(float r, float g, float b, float a) { } void SkyboxRenderStage::resize(size_t width, size_t height) { - this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); + this->output_texture->resize(width, height); auto fbo = this->renderer->create_texture_target({this->output_texture}); this->render_pass->set_target(fbo); diff --git a/libopenage/renderer/stages/terrain/render_stage.cpp b/libopenage/renderer/stages/terrain/render_stage.cpp index 01376d886b..d902475587 100644 --- a/libopenage/renderer/stages/terrain/render_stage.cpp +++ b/libopenage/renderer/stages/terrain/render_stage.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2024 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. #include "render_stage.h" @@ -13,6 +13,7 @@ #include "renderer/stages/terrain/chunk.h" #include "renderer/stages/terrain/mesh.h" #include "renderer/stages/terrain/model.h" +#include "renderer/texture.h" #include "renderer/window.h" #include "time/clock.h" @@ -88,8 +89,8 @@ void TerrainRenderStage::update() { } void TerrainRenderStage::resize(size_t width, size_t height) { - this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); - this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); + this->output_texture->resize(width, height); + this->depth_texture->resize(width, height); auto fbo = this->renderer->create_texture_target({this->output_texture, this->depth_texture}); this->render_pass->set_target(fbo); diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index 082b00cad1..147706d8a5 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -109,9 +109,9 @@ void WorldRenderStage::update() { } void WorldRenderStage::resize(size_t width, size_t height) { - this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); - this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); - this->id_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::r32ui)); + this->output_texture->resize(width, height); + this->depth_texture->resize(width, height); + this->id_texture->resize(width, height); auto fbo = this->renderer->create_texture_target({this->output_texture, this->id_texture, this->depth_texture}); this->render_pass->set_target(fbo); From 3f6c18e199e8a1b8249ea8c441f94d526d979586 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 23:33:06 +0200 Subject: [PATCH 063/118] input: Remove unnecessary resize callback for ID texture. --- libopenage/input/input_manager.cpp | 4 ---- libopenage/input/input_manager.h | 14 -------------- libopenage/presenter/presenter.cpp | 9 +-------- 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/libopenage/input/input_manager.cpp b/libopenage/input/input_manager.cpp index b3198fd520..cdcdae600e 100644 --- a/libopenage/input/input_manager.cpp +++ b/libopenage/input/input_manager.cpp @@ -155,10 +155,6 @@ bool InputManager::process(const QEvent &ev) { return false; } -void InputManager::set_id_texture(const std::shared_ptr &id_texture) { - this->game_controller->set_id_texture(id_texture); -} - void InputManager::process_action(const input::Event &ev, const input_action &action, const std::shared_ptr &ctx) { diff --git a/libopenage/input/input_manager.h b/libopenage/input/input_manager.h index c28372a198..76d7ac1c4c 100644 --- a/libopenage/input/input_manager.h +++ b/libopenage/input/input_manager.h @@ -18,10 +18,6 @@ class GuiInput; namespace openage { -namespace renderer { -class Texture2d; -} // namespace renderer - namespace input { namespace camera { @@ -155,16 +151,6 @@ class InputManager { */ bool process(const QEvent &ev); - /** - * Set the texture that maps pixels to entity IDs. - * - * Each pixel value in the texture corresponds to an entity ID. This - * mapping may be used for interacting with entities in the game world. - * - * @param id_texture ID texture. - */ - void set_id_texture(const std::shared_ptr &id_texture); - private: /** * Process the (default) action for an input event. diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index 118b2f0c2d..baecb2ee92 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -255,14 +255,7 @@ void Presenter::init_input() { input_ctx->set_game_bindings(engine_context); auto id_texture = this->world_renderer->get_id_texture(); - this->input_manager->set_id_texture(id_texture); - - window->add_resize_callback([&](size_t /* width */, size_t /* height */, double /*scale*/) { - // TODO: We must guarantee that this happens AFTER the world renderer - // has resized its textures. - auto id_texture = this->world_renderer->get_id_texture(); - this->input_manager->set_id_texture(id_texture); - }); + game_controller->set_id_texture(id_texture); } // attach GUI if it's initialized From acadb96bc4d774a312dc0c2f9fc9939fd83c584e Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 23:37:22 +0200 Subject: [PATCH 064/118] renderer: Check if all textures of render target have the same size. --- libopenage/renderer/opengl/render_target.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libopenage/renderer/opengl/render_target.cpp b/libopenage/renderer/opengl/render_target.cpp index b965dc58ea..a0e4862cf4 100644 --- a/libopenage/renderer/opengl/render_target.cpp +++ b/libopenage/renderer/opengl/render_target.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "render_target.h" @@ -22,8 +22,15 @@ GlRenderTarget::GlRenderTarget(const std::shared_ptr &context, type(gl_render_target_t::framebuffer), framebuffer({context, textures}), textures(textures) { - // TODO: Check if the textures are all the same size - this->size = this->textures.value().at(0)->get_info().get_size(); + // Check if the textures are all the same size + auto size = this->textures.value().at(0)->get_info().get_size(); + for (const auto &tex : this->textures.value()) { + if (tex->get_info().get_size() != size) { + throw Error{ERR << "All texture targets must be the same size."}; + } + } + + this->size = size; log::log(MSG(dbg) << "Created OpenGL render target for textures"); } From b22e7af241849c54b9323eef18a418e5c274783c Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 22 Apr 2025 00:08:01 +0200 Subject: [PATCH 065/118] renderer: Figure out attachment points with switch command. --- libopenage/renderer/opengl/framebuffer.cpp | 38 +++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/libopenage/renderer/opengl/framebuffer.cpp b/libopenage/renderer/opengl/framebuffer.cpp index 0ad46c1563..6dd39167d7 100644 --- a/libopenage/renderer/opengl/framebuffer.cpp +++ b/libopenage/renderer/opengl/framebuffer.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "framebuffer.h" @@ -30,26 +30,42 @@ GlFramebuffer::GlFramebuffer(const std::shared_ptr &context, glBindFramebuffer(GL_FRAMEBUFFER, handle); - std::vector drawBuffers; + std::vector draw_buffers; if (textures.empty()) { throw Error{ERR << "At least 1 texture must be assigned to texture framebuffer."}; } - size_t colorTextureCount = 0; + size_t color_texture_count = 0; + size_t depth_texture_count = 0; for (auto const &texture : textures) { - // TODO figure out attachment points from pixel formats - if (texture->get_info().get_format() == resources::pixel_format::depth24) { + auto fmt = texture->get_info().get_format(); + switch (fmt) { + case resources::pixel_format::depth24: + depth_texture_count += 1; + if (depth_texture_count > 1) { + log::log(WARN << "Framebuffer already has one depth texture attached. " + << "Assignment of additional depth texture ignored."); + break; + } glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture->get_handle(), 0); - } - else { - auto attachmentPoint = GL_COLOR_ATTACHMENT0 + colorTextureCount++; - glFramebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, GL_TEXTURE_2D, texture->get_handle(), 0); - drawBuffers.push_back(attachmentPoint); + break; + case resources::pixel_format::r16ui: + case resources::pixel_format::r32ui: + case resources::pixel_format::rgba8: + case resources::pixel_format::rgb8: + case resources::pixel_format::bgr8: + case resources::pixel_format::rgba8ui: { + auto attachment_point = GL_COLOR_ATTACHMENT0 + color_texture_count++; + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment_point, GL_TEXTURE_2D, texture->get_handle(), 0); + draw_buffers.push_back(attachment_point); + } break; + default: + throw Error{ERR << "Unsupported pixel format for framebuffer texture."}; } } - glDrawBuffers(drawBuffers.size(), drawBuffers.data()); + glDrawBuffers(draw_buffers.size(), draw_buffers.data()); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { throw Error(MSG(err) << "Could not create OpenGL framebuffer."); From 5aa35b9fe6ab8c3f6a58aa860d8d0e40202a07c9 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 22 Apr 2025 23:45:19 +0200 Subject: [PATCH 066/118] gamestate: Reserve some game entity IDs for internal use. This allows using 0 in the ID texture as respresentation for "no entity". --- libopenage/gamestate/entity_factory.cpp | 2 -- libopenage/gamestate/entity_factory.h | 10 +++++++--- libopenage/gamestate/event/drag_select.cpp | 4 ++-- libopenage/gamestate/event/spawn_entity.cpp | 2 +- libopenage/input/controller/game/controller.cpp | 8 ++++---- libopenage/input/controller/game/controller.h | 10 +++++----- libopenage/presenter/presenter.cpp | 4 ++-- 7 files changed, 21 insertions(+), 19 deletions(-) diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index dcd59293a4..4a4b6ac2d0 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -110,8 +110,6 @@ std::shared_ptr create_test_activity() { } EntityFactory::EntityFactory() : - next_entity_id{0}, - next_player_id{0}, render_factory{nullptr} { } diff --git a/libopenage/gamestate/entity_factory.h b/libopenage/gamestate/entity_factory.h index 11efbe3f2e..7ac3f4a7f1 100644 --- a/libopenage/gamestate/entity_factory.h +++ b/libopenage/gamestate/entity_factory.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -118,13 +118,17 @@ class EntityFactory { /** * ID of the next game entity to be created. + * + * IDs 0-99 are reserved. */ - entity_id_t next_entity_id; + entity_id_t next_entity_id = 100; /** * ID of the next player to be created. + * + * ID 0 is reserved. */ - player_id_t next_player_id; + player_id_t next_player_id = 1; /** * Factory for creating connector objects to the renderer which make game entities displayable. diff --git a/libopenage/gamestate/event/drag_select.cpp b/libopenage/gamestate/event/drag_select.cpp index efb43b0ab1..1100b5088a 100644 --- a/libopenage/gamestate/event/drag_select.cpp +++ b/libopenage/gamestate/event/drag_select.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "drag_select.h" @@ -32,7 +32,7 @@ void DragSelectHandler::invoke(openage::event::EventLoop & /* loop */, const param_map ¶ms) { auto gstate = std::dynamic_pointer_cast(state); - size_t controlled_id = params.get("controlled", 0); + auto controlled_id = params.get("controlled", 0); Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); Eigen::Matrix4f cam_matrix = params.get("camera_matrix", id_matrix); diff --git a/libopenage/gamestate/event/spawn_entity.cpp b/libopenage/gamestate/event/spawn_entity.cpp index 45dbb08f64..94bd9f8a7e 100644 --- a/libopenage/gamestate/event/spawn_entity.cpp +++ b/libopenage/gamestate/event/spawn_entity.cpp @@ -193,7 +193,7 @@ void SpawnEntityHandler::invoke(openage::event::EventLoop & /* loop */, } // Create entity - player_id_t owner_id = params.get("owner", 0); + auto owner_id = params.get("owner", 0); auto entity = this->factory->add_game_entity(this->loop, gstate, owner_id, nyan_entity); // Setup components diff --git a/libopenage/input/controller/game/controller.cpp b/libopenage/input/controller/game/controller.cpp index f23d268397..4f7bd41e3f 100644 --- a/libopenage/input/controller/game/controller.cpp +++ b/libopenage/input/controller/game/controller.cpp @@ -24,13 +24,13 @@ namespace openage::input::game { -Controller::Controller(const std::unordered_set &controlled_factions, - size_t active_faction_id) : +Controller::Controller(const std::unordered_set &controlled_factions, + gamestate::player_id_t active_faction_id) : controlled_factions{controlled_factions}, active_faction_id{active_faction_id}, outqueue{} {} -void Controller::set_control(size_t faction_id) { +void Controller::set_control(gamestate::player_id_t faction_id) { std::unique_lock lock{this->mutex}; if (this->controlled_factions.find(faction_id) != this->controlled_factions.end()) { @@ -38,7 +38,7 @@ void Controller::set_control(size_t faction_id) { } } -size_t Controller::get_controlled() const { +gamestate::player_id_t Controller::get_controlled() const { std::unique_lock lock{this->mutex}; return this->active_faction_id; diff --git a/libopenage/input/controller/game/controller.h b/libopenage/input/controller/game/controller.h index ddab1101af..a6740030e2 100644 --- a/libopenage/input/controller/game/controller.h +++ b/libopenage/input/controller/game/controller.h @@ -46,8 +46,8 @@ class Controller : public std::enable_shared_from_this { * @param controlled_factions Factions that can be managed by the controller. * @param active_faction_id Current active faction ID. */ - Controller(const std::unordered_set &controlled_factions, - size_t active_faction_id); + Controller(const std::unordered_set &controlled_factions, + gamestate::player_id_t active_faction_id); ~Controller() = default; @@ -57,7 +57,7 @@ class Controller : public std::enable_shared_from_this { * * @param faction_id ID of the new active faction. */ - void set_control(size_t faction_id); + void set_control(gamestate::player_id_t faction_id); /** * Get the ID of the faction actively controlled by the controller. @@ -133,12 +133,12 @@ class Controller : public std::enable_shared_from_this { /** * Factions controllable by this controller. */ - std::unordered_set controlled_factions; + std::unordered_set controlled_factions; /** * ID of the currently active faction. */ - size_t active_faction_id; + gamestate::player_id_t active_faction_id; /** * Currently selected entities. diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index baecb2ee92..0cd3122478 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -244,8 +244,8 @@ void Presenter::init_input() { log::log(INFO << "Loading game simulation controls"); // TODO: Remove hardcoding for controlled/active factions - std::unordered_set controlled_factions{0, 1, 2, 3}; - size_t active_faction_id = 0; + std::unordered_set controlled_factions{1, 2, 3, 4}; + gamestate::player_id_t active_faction_id = 1; auto game_controller = std::make_shared( controlled_factions, active_faction_id); From 0b0e0982c9e84a95b79b2629f107b07a53c66840 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 4 May 2025 16:58:56 +0200 Subject: [PATCH 067/118] convert: Change ability with range to use Ranged property. --- .../conversion/aoc/ability_subprocessor.py | 333 +++++++++++------- .../aoc/upgrade_ability_subprocessor.py | 237 +++++++++---- .../aoc/upgrade_attribute_subprocessor.py | 22 +- .../aoc/upgrade_resource_subprocessor.py | 6 +- .../conversion/ror/ability_subprocessor.py | 74 ++-- .../ror/upgrade_ability_subprocessor.py | 81 +++-- .../conversion/swgbcc/ability_subprocessor.py | 38 +- .../swgbcc/upgrade_resource_subprocessor.py | 6 +- .../convert/service/read/nyan_api_loader.py | 55 +-- 9 files changed, 543 insertions(+), 309 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/ability_subprocessor.py b/openage/convert/processor/conversion/aoc/ability_subprocessor.py index 120541fe4c..cf6f0a01b4 100644 --- a/openage/convert/processor/conversion/aoc/ability_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/ability_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-public-methods,too-many-lines,too-many-locals # pylint: disable=too-many-branches,too-many-statements,too-many-arguments @@ -56,6 +56,45 @@ def active_transform_to_ability(line: GenieGameEntityGroup) -> ForwardRef: # TODO: Implement return None + @staticmethod + def activity_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Activity ability to a line. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :returns: The forward reference for the ability. + :rtype: ...dataformat.forward_ref.ForwardRef + """ + current_unit_id = line.get_head_unit_id() + + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.Activity" + ability_raw_api_object = RawAPIObject(ability_ref, "Activity", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Activity") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # activity graph + if isinstance(line, GenieUnitLineGroup): + activity = dataset.pregen_nyan_objects["util.activity.types.Unit"].get_nyan_object() + + else: + activity = dataset.pregen_nyan_objects["util.activity.types.Default"].get_nyan_object() + + ability_raw_api_object.add_raw_member("graph", activity, "engine.ability.type.Activity") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref + @staticmethod def apply_continuous_effect_ability( line: GenieGameEntityGroup, @@ -88,15 +127,9 @@ def apply_continuous_effect_ability( ability_name = command_lookup_dict[command_id][0] - if ranged: - ability_parent = "engine.ability.type.RangedContinuousEffect" - - else: - ability_parent = "engine.ability.type.ApplyContinuousEffect" - ability_ref = f"{game_entity_name}.{ability_name}" ability_raw_api_object = RawAPIObject(ability_ref, ability_name, dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent(ability_parent) + ability_raw_api_object.add_raw_parent("engine.ability.type.ApplyContinuousEffect") ability_location = ForwardRef(line, game_entity_name) ability_raw_api_object.set_location(ability_location) @@ -234,12 +267,24 @@ def apply_continuous_effect_ability( properties, "engine.ability.Ability") + # Range if ranged: + # Range + property_ref = f"{ability_ref}.Ranged" + property_raw_api_object = RawAPIObject(property_ref, + "Ranged", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Ranged") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + # Min range min_range = current_unit["weapon_range_min"].value - ability_raw_api_object.add_raw_member("min_range", - min_range, - "engine.ability.type.RangedContinuousEffect") + property_raw_api_object.add_raw_member("min_range", + min_range, + "engine.ability.property.type.Ranged") # Max range if command_id == 105: @@ -249,9 +294,14 @@ def apply_continuous_effect_ability( else: max_range = current_unit["weapon_range_max"].value - ability_raw_api_object.add_raw_member("max_range", - max_range, - "engine.ability.type.RangedContinuousEffect") + property_raw_api_object.add_raw_member("max_range", + max_range, + "engine.ability.property.type.Ranged") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + dataset.nyan_api_objects["engine.ability.property.type.Ranged"]: property_forward_ref + }) # Effects if command_id == 101: @@ -302,45 +352,6 @@ def apply_continuous_effect_ability( return ability_forward_ref - @staticmethod - def activity_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Activity ability to a line. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward reference for the ability. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - current_unit_id = line.get_head_unit_id() - - dataset = line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.Activity" - ability_raw_api_object = RawAPIObject(ability_ref, "Activity", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Activity") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # activity graph - if isinstance(line, GenieUnitLineGroup): - activity = dataset.pregen_nyan_objects["util.activity.types.Unit"].get_nyan_object() - - else: - activity = dataset.pregen_nyan_objects["util.activity.types.Default"].get_nyan_object() - - ability_raw_api_object.add_raw_member("graph", activity, "engine.ability.type.Activity") - - line.add_raw_api_object(ability_raw_api_object) - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - @staticmethod def apply_discrete_effect_ability( line: GenieGameEntityGroup, @@ -376,18 +387,12 @@ def apply_discrete_effect_ability( ability_name = command_lookup_dict[command_id][0] - if ranged: - ability_parent = "engine.ability.type.RangedDiscreteEffect" - - else: - ability_parent = "engine.ability.type.ApplyDiscreteEffect" - if projectile == -1: ability_ref = f"{game_entity_name}.{ability_name}" ability_raw_api_object = RawAPIObject(ability_ref, ability_name, dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent(ability_parent) + ability_raw_api_object.add_raw_parent("engine.ability.type.ApplyDiscreteEffect") ability_location = ForwardRef(line, game_entity_name) ability_raw_api_object.set_location(ability_location) @@ -398,7 +403,7 @@ def apply_discrete_effect_ability( f"{ability_name}") ability_raw_api_object = RawAPIObject( ability_ref, ability_name, dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent(ability_parent) + ability_raw_api_object.add_raw_parent("engine.ability.type.ApplyDiscreteEffect") ability_location = ForwardRef( line, f"{game_entity_name}.ShootProjectile.Projectile{projectile}" @@ -540,18 +545,35 @@ def apply_discrete_effect_ability( properties, "engine.ability.Ability") + # Range if ranged: + # Range + property_ref = f"{ability_ref}.Ranged" + property_raw_api_object = RawAPIObject(property_ref, + "Ranged", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Ranged") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + # Min range min_range = current_unit["weapon_range_min"].value - ability_raw_api_object.add_raw_member("min_range", - min_range, - "engine.ability.type.RangedDiscreteEffect") + property_raw_api_object.add_raw_member("min_range", + min_range, + "engine.ability.property.type.Ranged") # Max range max_range = current_unit["weapon_range_max"].value - ability_raw_api_object.add_raw_member("max_range", - max_range, - "engine.ability.type.RangedDiscreteEffect") + property_raw_api_object.add_raw_member("max_range", + max_range, + "engine.ability.property.type.Ranged") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + dataset.nyan_api_objects["engine.ability.property.type.Ranged"]: property_forward_ref + }) # Effects batch_ref = f"{ability_ref}.Batch" @@ -820,7 +842,7 @@ def collect_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def collision_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Collision ability to a line. @@ -2165,7 +2187,7 @@ def constructable_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def create_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Create ability to a line. @@ -2239,7 +2261,7 @@ def create_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def death_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds a PassiveTransformTo ability to a line that is used to make entities die. @@ -2516,7 +2538,7 @@ def death_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def delete_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds a PassiveTransformTo ability to a line that is used to make entities die. @@ -2627,7 +2649,7 @@ def delete_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def despawn_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Despawn ability to a line. @@ -2778,7 +2800,7 @@ def despawn_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def drop_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the DropResources ability to a line. @@ -2885,7 +2907,7 @@ def drop_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def drop_site_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the DropSite ability to a line. @@ -2962,7 +2984,7 @@ def drop_site_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def enter_container_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the EnterContainer ability to a line. @@ -3029,7 +3051,7 @@ def enter_container_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def exchange_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the ExchangeResources ability to a line. @@ -3096,7 +3118,7 @@ def exchange_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: return abilities - @ staticmethod + @staticmethod def exit_container_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the ExitContainer ability to a line. @@ -3150,7 +3172,7 @@ def exit_container_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def game_entity_stance_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the GameEntityStance ability to a line. @@ -3237,7 +3259,7 @@ def game_entity_stance_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def formation_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Formation ability to a line. @@ -3320,7 +3342,7 @@ def formation_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def foundation_ability(line: GenieGameEntityGroup, terrain_id: int = -1) -> ForwardRef: """ Adds the Foundation abilities to a line. Optionally chooses the specified @@ -3364,7 +3386,7 @@ def foundation_ability(line: GenieGameEntityGroup, terrain_id: int = -1) -> Forw return ability_forward_ref - @ staticmethod + @staticmethod def gather_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Gather abilities to a line. Unlike the other methods, this @@ -3601,7 +3623,7 @@ def gather_ability(line: GenieGameEntityGroup) -> ForwardRef: return abilities - @ staticmethod + @staticmethod def harvestable_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Harvestable ability to a line. @@ -3982,7 +4004,7 @@ def harvestable_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def herd_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Herd ability to a line. @@ -4005,11 +4027,6 @@ def herd_ability(line: GenieGameEntityGroup) -> ForwardRef: ability_location = ForwardRef(line, game_entity_name) ability_raw_api_object.set_location(ability_location) - # Range - ability_raw_api_object.add_raw_member("range", - 3.0, - "engine.ability.type.Herd") - # Strength ability_raw_api_object.add_raw_member("strength", 0, @@ -4028,13 +4045,45 @@ def herd_ability(line: GenieGameEntityGroup) -> ForwardRef: [], "engine.ability.type.Herd") + properties = {} + + # Ranged property + property_ref = f"{ability_ref}.Ranged" + property_raw_api_object = RawAPIObject(property_ref, + "Ranged", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Ranged") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + property_raw_api_object.add_raw_member("min_range", + 0.0, + "engine.ability.property.type.Ranged") + property_raw_api_object.add_raw_member("max_range", + 3.0, # hardcoded + "engine.ability.property.type.Ranged") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + dataset.nyan_api_objects["engine.ability.property.type.Ranged"]: property_forward_ref + }) + + # TODO: Animated property + # animation seems to be hardcoded? + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + line.add_raw_api_object(ability_raw_api_object) ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) return ability_forward_ref - @ staticmethod + @staticmethod def herdable_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Herdable ability to a line. @@ -4074,7 +4123,7 @@ def herdable_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def idle_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Idle ability to a line. @@ -4179,7 +4228,7 @@ def idle_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def live_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Live ability to a line. @@ -4285,7 +4334,7 @@ def live_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def los_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the LineOfSight ability to a line. @@ -4344,7 +4393,7 @@ def los_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def move_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Move ability to a line. @@ -4543,7 +4592,7 @@ def move_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def move_projectile_ability(line: GenieGameEntityGroup, position: int = -1) -> ForwardRef: """ Adds the Move ability to a projectile of the specified line. @@ -4640,7 +4689,7 @@ def move_projectile_ability(line: GenieGameEntityGroup, position: int = -1) -> F return ability_forward_ref - @ staticmethod + @staticmethod def named_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Named ability to a line. @@ -4732,7 +4781,7 @@ def named_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def overlay_terrain_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the OverlayTerrain to a line. @@ -4773,7 +4822,7 @@ def overlay_terrain_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def pathable_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Pathable ability to a line. @@ -4820,7 +4869,7 @@ def pathable_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def production_queue_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the ProductionQueue ability to a line. @@ -4880,7 +4929,7 @@ def production_queue_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def projectile_ability(line: GenieGameEntityGroup, position: int = 0) -> ForwardRef: """ Adds a Projectile ability to projectiles in a line. Which projectile should @@ -4991,7 +5040,7 @@ def projectile_ability(line: GenieGameEntityGroup, position: int = 0) -> Forward return ability_forward_ref - @ staticmethod + @staticmethod def provide_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the ProvideContingent ability to a line. @@ -5070,7 +5119,7 @@ def provide_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def rally_point_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the RallyPoint ability to a line. @@ -5099,7 +5148,7 @@ def rally_point_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def regenerate_attribute_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the RegenerateAttribute ability to a line. @@ -5180,7 +5229,7 @@ def regenerate_attribute_ability(line: GenieGameEntityGroup) -> ForwardRef: return [ability_forward_ref] - @ staticmethod + @staticmethod def regenerate_resource_spot_ability(line: GenieGameEntityGroup) -> None: """ Adds the RegenerateResourceSpot ability to a line. @@ -5192,7 +5241,7 @@ def regenerate_resource_spot_ability(line: GenieGameEntityGroup) -> None: """ # Unused in AoC - @ staticmethod + @staticmethod def remove_storage_ability(line) -> ForwardRef: """ Adds the RemoveStorage ability to a line. @@ -5242,7 +5291,7 @@ def remove_storage_ability(line) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def restock_ability(line: GenieGameEntityGroup, restock_target_id: int) -> ForwardRef: """ Adds the Restock ability to a line. @@ -5380,7 +5429,7 @@ def restock_ability(line: GenieGameEntityGroup, restock_target_id: int) -> Forwa return ability_forward_ref - @ staticmethod + @staticmethod def research_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Research ability to a line. @@ -5456,7 +5505,7 @@ def research_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def resistance_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Resistance ability to a line. @@ -5506,7 +5555,7 @@ def resistance_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the ResourceStorage ability to a line. @@ -5755,7 +5804,7 @@ def resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def selectable_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds Selectable abilities to a line. Units will get two of these, @@ -5976,7 +6025,7 @@ def selectable_ability(line: GenieGameEntityGroup) -> ForwardRef: return abilities - @ staticmethod + @staticmethod def send_back_to_task_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the SendBackToTask ability to a line. @@ -6017,7 +6066,7 @@ def send_back_to_task_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> ForwardRef: """ Adds the ShootProjectile ability to a line. @@ -6051,6 +6100,31 @@ def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> For # Ability properties properties = {} + # Range + property_ref = f"{ability_ref}.Ranged" + property_raw_api_object = RawAPIObject(property_ref, + "Ranged", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Ranged") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + min_range = current_unit["weapon_range_min"].value + property_raw_api_object.add_raw_member("min_range", + min_range, + "engine.ability.property.type.Ranged") + max_range = current_unit["weapon_range_max"].value + property_raw_api_object.add_raw_member("max_range", + max_range, + "engine.ability.property.type.Ranged") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + dataset.nyan_api_objects["engine.ability.property.type.Ranged"]: property_forward_ref + }) + # Animation ability_animation_id = current_unit["attack_sprite_id"].value if ability_animation_id > -1: @@ -6178,17 +6252,6 @@ def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> For max_projectiles, "engine.ability.type.ShootProjectile") - # Range - min_range = current_unit["weapon_range_min"].value - ability_raw_api_object.add_raw_member("min_range", - min_range, - "engine.ability.type.ShootProjectile") - - max_range = current_unit["weapon_range_max"].value - ability_raw_api_object.add_raw_member("max_range", - max_range, - "engine.ability.type.ShootProjectile") - # Reload time and delay reload_time = current_unit["attack_speed"].value ability_raw_api_object.add_raw_member("reload_time", @@ -6276,7 +6339,7 @@ def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> For return ability_forward_ref - @ staticmethod + @staticmethod def stop_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Stop ability to a line. @@ -6330,7 +6393,7 @@ def stop_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def storage_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Storage ability to a line. @@ -6767,7 +6830,7 @@ def storage_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def terrain_requirement_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the TerrainRequirement to a line. @@ -6820,7 +6883,7 @@ def terrain_requirement_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def trade_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Trade ability to a line. @@ -6884,7 +6947,7 @@ def trade_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def trade_post_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the TradePost ability to a line. @@ -6951,7 +7014,7 @@ def trade_post_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def transfer_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the TransferStorage ability to a line. @@ -7030,7 +7093,7 @@ def transfer_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def turn_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Turn ability to a line. @@ -7104,7 +7167,7 @@ def turn_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def use_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the UseContingent ability to a line. @@ -7180,7 +7243,7 @@ def use_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def visibility_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the Visibility ability to a line. @@ -7299,7 +7362,7 @@ def visibility_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod + @staticmethod def create_animation( line: GenieGameEntityGroup, animation_id: int, @@ -7355,7 +7418,7 @@ def create_animation( return animation_forward_ref - @ staticmethod + @staticmethod def create_civ_animation( line: GenieGameEntityGroup, civ_group: GenieCivilizationGroup, @@ -7465,7 +7528,7 @@ def create_civ_animation( [wrapper_forward_ref]) civ_group.add_raw_member_push(push_object) - @ staticmethod + @staticmethod def create_sound( line: GenieGameEntityGroup, sound_id: int, @@ -7530,7 +7593,7 @@ def create_sound( return sound_forward_ref - @ staticmethod + @staticmethod def create_language_strings( line: GenieGameEntityGroup, string_id: int, diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py b/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py index f00814db31..a1287ba4ac 100644 --- a/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals,too-many-lines,too-many-statements,invalid-name # pylint: disable=too-many-public-methods,too-many-branches,too-many-arguments @@ -84,16 +84,71 @@ def apply_continuous_effect_ability( # Command types Heal, Construct, Repair are not upgraded by lines - diff_min_range = None - diff_max_range = None - if not data_changed and ranged: + if ranged: diff_min_range = diff["weapon_range_min"] diff_max_range = diff["weapon_range_max"] + if any(not isinstance(value, NoDiffMember) for value in ( diff_min_range, diff_max_range )): - data_changed = True + patch_target_ref = f"{game_entity_name}.{ability_name}.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{ability_name}RangedWrapper" + wrapper_ref = f"{container_obj_ref}.{wrapper_name}" + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + if isinstance(line, GenieBuildingLineGroup): + wrapper_raw_api_object.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper_raw_api_object.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + else: + wrapper_raw_api_object.set_location(ForwardRef(converter_group, + container_obj_ref)) + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}{ability_name}Ranged" + nyan_patch_ref = ForwardRef(line, nyan_patch_name) + nyan_patch_location = ForwardRef(converter_group, wrapper_ref) + nyan_patch_raw_api_object = RawAPIObject(nyan_patch_ref, + nyan_patch_name, + dataset.nyan_api_objects, + nyan_patch_location) + nyan_patch_raw_api_object.add_raw_parent("engine.util.patch.NyanPatch") + nyan_patch_raw_api_object.set_patch_target(patch_target_forward_ref) + + if not isinstance(diff_min_range, NoDiffMember): + min_range = diff_min_range.value + nyan_patch_raw_api_object.add_raw_patch_member( + "min_range", + min_range, + "engine.ability.property.type.Ranged", + MemberOperator.ADD) + + if not isinstance(diff_max_range, NoDiffMember): + max_range = diff_max_range.value + nyan_patch_raw_api_object.add_raw_patch_member( + "max_range", + max_range, + "engine.ability.property.type.Ranged", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + converter_group.add_raw_api_object(wrapper_raw_api_object) + converter_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(converter_group, wrapper_ref) + patches.append(wrapper_forward_ref) if not isinstance(diff_animation, NoDiffMember): diff_animation_id = diff_animation.value @@ -195,23 +250,6 @@ def apply_continuous_effect_ability( "engine.ability.type.ApplyContinuousEffect", MemberOperator.ASSIGN) - if ranged: - if not isinstance(diff_min_range, NoDiffMember): - min_range = diff_min_range.value - - nyan_patch_raw_api_object.add_raw_patch_member("min_range", - min_range, - "engine.ability.type.RangedContinuousEffect", - MemberOperator.ADD) - - if not isinstance(diff_max_range, NoDiffMember): - max_range = diff_max_range.value - - nyan_patch_raw_api_object.add_raw_patch_member("max_range", - max_range, - "engine.ability.type.RangedContinuousEffect", - MemberOperator.ADD) - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) wrapper_raw_api_object.add_raw_member("patch", patch_forward_ref, @@ -270,16 +308,71 @@ def apply_discrete_effect_ability( diff_frame_delay)): data_changed = True - diff_min_range = None - diff_max_range = None if ranged: diff_min_range = diff["weapon_range_min"] diff_max_range = diff["weapon_range_max"] + if any(not isinstance(value, NoDiffMember) for value in ( diff_min_range, diff_max_range )): - data_changed = True + patch_target_ref = f"{game_entity_name}.{ability_name}.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{ability_name}RangedWrapper" + wrapper_ref = f"{container_obj_ref}.{wrapper_name}" + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + if isinstance(line, GenieBuildingLineGroup): + wrapper_raw_api_object.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper_raw_api_object.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + else: + wrapper_raw_api_object.set_location(ForwardRef(converter_group, + container_obj_ref)) + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}{ability_name}Ranged" + nyan_patch_ref = ForwardRef(line, nyan_patch_name) + nyan_patch_location = ForwardRef(converter_group, wrapper_ref) + nyan_patch_raw_api_object = RawAPIObject(nyan_patch_ref, + nyan_patch_name, + dataset.nyan_api_objects, + nyan_patch_location) + nyan_patch_raw_api_object.add_raw_parent("engine.util.patch.NyanPatch") + nyan_patch_raw_api_object.set_patch_target(patch_target_forward_ref) + + if not isinstance(diff_min_range, NoDiffMember): + min_range = diff_min_range.value + nyan_patch_raw_api_object.add_raw_patch_member( + "min_range", + min_range, + "engine.ability.property.type.Ranged", + MemberOperator.ADD) + + if not isinstance(diff_max_range, NoDiffMember): + max_range = diff_max_range.value + nyan_patch_raw_api_object.add_raw_patch_member( + "max_range", + max_range, + "engine.ability.property.type.Ranged", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + converter_group.add_raw_api_object(wrapper_raw_api_object) + converter_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(converter_group, wrapper_ref) + patches.append(wrapper_forward_ref) if not isinstance(diff_animation, NoDiffMember): diff_animation_id = diff_animation.value @@ -389,23 +482,6 @@ def apply_discrete_effect_ability( "engine.ability.type.ApplyDiscreteEffect", MemberOperator.ASSIGN) - if ranged: - if not isinstance(diff_min_range, NoDiffMember): - min_range = diff_min_range.value - - nyan_patch_raw_api_object.add_raw_patch_member("min_range", - min_range, - "engine.ability.type.RangedApplyDiscreteEffect", - MemberOperator.ADD) - - if not isinstance(diff_max_range, NoDiffMember): - max_range = diff_max_range.value - - nyan_patch_raw_api_object.add_raw_patch_member("max_range", - max_range, - "engine.ability.type.RangedApplyDiscreteEffect", - MemberOperator.ADD) - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) wrapper_raw_api_object.add_raw_member("patch", patch_forward_ref, @@ -1409,7 +1485,7 @@ def shoot_projectile_ability( diff: ConverterObject = None ) -> list[ForwardRef]: """ - Creates a patch for the Selectable ability of a line. + Creates a patch for the ShootProjectile ability of a line. :param converter_group: Group that gets the patch. :type converter_group: ...dataformat.converter_object.ConverterObjectGroup @@ -1454,8 +1530,6 @@ def shoot_projectile_ability( if any(not isinstance(value, NoDiffMember) for value in ( diff_min_projectiles, diff_max_projectiles, - diff_min_range, - diff_max_range, diff_reload_time, diff_spawn_delay, diff_spawn_area_offsets, @@ -1465,6 +1539,65 @@ def shoot_projectile_ability( )): data_changed = True + if any(not isinstance(value, NoDiffMember) for value in ( + diff_min_range, + diff_max_range + )): + patch_target_ref = f"{game_entity_name}.{ability_name}.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{ability_name}RangedWrapper" + wrapper_ref = f"{container_obj_ref}.{wrapper_name}" + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + if isinstance(line, GenieBuildingLineGroup): + wrapper_raw_api_object.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper_raw_api_object.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + else: + wrapper_raw_api_object.set_location(ForwardRef(converter_group, container_obj_ref)) + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}{ability_name}Ranged" + nyan_patch_ref = ForwardRef(line, nyan_patch_name) + nyan_patch_location = ForwardRef(converter_group, wrapper_ref) + nyan_patch_raw_api_object = RawAPIObject(nyan_patch_ref, + nyan_patch_name, + dataset.nyan_api_objects, + nyan_patch_location) + nyan_patch_raw_api_object.add_raw_parent("engine.util.patch.NyanPatch") + nyan_patch_raw_api_object.set_patch_target(patch_target_forward_ref) + + if not isinstance(diff_min_range, NoDiffMember): + min_range = diff_min_range.value + nyan_patch_raw_api_object.add_raw_patch_member("min_range", + min_range, + "engine.ability.property.type.Ranged", + MemberOperator.ADD) + + if not isinstance(diff_max_range, NoDiffMember): + max_range = diff_max_range.value + nyan_patch_raw_api_object.add_raw_patch_member("max_range", + max_range, + "engine.ability.property.type.Ranged", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + converter_group.add_raw_api_object(wrapper_raw_api_object) + converter_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(converter_group, wrapper_ref) + patches.append(wrapper_forward_ref) + if not isinstance(diff_animation, NoDiffMember): diff_animation_id = diff_animation.value @@ -1592,20 +1725,6 @@ def shoot_projectile_ability( "engine.ability.type.ShootProjectile", MemberOperator.ADD) - if not isinstance(diff_min_range, NoDiffMember): - min_range = diff_min_range.value - nyan_patch_raw_api_object.add_raw_patch_member("min_range", - min_range, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_max_range, NoDiffMember): - max_range = diff_max_range.value - nyan_patch_raw_api_object.add_raw_patch_member("max_range", - max_range, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - if not isinstance(diff_reload_time, NoDiffMember): reload_time = diff_reload_time.value nyan_patch_raw_api_object.add_raw_patch_member("reload_time", diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py b/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py index f8c185d5a8..659d76dfd6 100644 --- a/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals,too-many-lines,too-many-statements,too-many-public-methods # @@ -1786,25 +1786,23 @@ def max_range_upgrade( game_entity_name = name_lookup_dict[head_unit_id][0] + patch_target_parent = "engine.ability.property.type.Ranged" if line.is_projectile_shooter(): - patch_target_ref = f"{game_entity_name}.Attack" + patch_target_ref = f"{game_entity_name}.Attack.Ranged" patch_target_forward_ref = ForwardRef(line, patch_target_ref) - patch_target_parent = "engine.ability.type.ShootProjectile" elif line.is_melee(): if line.is_ranged(): - patch_target_ref = f"{game_entity_name}.Attack" + patch_target_ref = f"{game_entity_name}.Attack.Ranged" patch_target_forward_ref = ForwardRef(line, patch_target_ref) - patch_target_parent = "engine.ability.type.RangedDiscreteEffect" else: # excludes ram upgrades return patches elif line.has_command(104): - patch_target_ref = f"{game_entity_name}.Convert" + patch_target_ref = f"{game_entity_name}.Convert.Ranged" patch_target_forward_ref = ForwardRef(line, patch_target_ref) - patch_target_parent = "engine.ability.type.RangedDiscreteEffect" else: # no matching ability @@ -1899,20 +1897,18 @@ def min_range_upgrade( game_entity_name = name_lookup_dict[head_unit_id][0] + patch_target_parent = "engine.ability.property.type.Ranged" if line.is_projectile_shooter(): - patch_target_ref = f"{game_entity_name}.Attack" + patch_target_ref = f"{game_entity_name}.Attack.Ranged" patch_target_forward_ref = ForwardRef(line, patch_target_ref) - patch_target_parent = "engine.ability.type.ShootProjectile" elif line.is_melee(): - patch_target_ref = f"{game_entity_name}.Attack" + patch_target_ref = f"{game_entity_name}.Attack.Ranged" patch_target_forward_ref = ForwardRef(line, patch_target_ref) - patch_target_parent = "engine.ability.type.RangedDiscreteEffect" elif line.has_command(104): - patch_target_ref = f"{game_entity_name}.Convert" + patch_target_ref = f"{game_entity_name}.Convert.Ranged" patch_target_forward_ref = ForwardRef(line, patch_target_ref) - patch_target_parent = "engine.ability.type.RangedDiscreteEffect" else: return [] diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource_subprocessor.py b/openage/convert/processor/conversion/aoc/upgrade_resource_subprocessor.py index 477c52ad1e..5bcb5abb7b 100644 --- a/openage/convert/processor/conversion/aoc/upgrade_resource_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/upgrade_resource_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals,too-many-lines,too-many-statements,too-many-public-methods,invalid-name # @@ -839,7 +839,7 @@ def heal_range_upgrade( game_entity_name = name_lookup_dict[monk_id][0] - patch_target_ref = f"{game_entity_name}.Heal" + patch_target_ref = f"{game_entity_name}.Heal.Ranged" patch_target_forward_ref = ForwardRef(line, patch_target_ref) # Wrapper @@ -865,7 +865,7 @@ def heal_range_upgrade( nyan_patch_raw_api_object.add_raw_patch_member("max_range", value, - "engine.ability.type.RangedContinuousEffect", + "engine.ability.property.type.Ranged", operator) patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) diff --git a/openage/convert/processor/conversion/ror/ability_subprocessor.py b/openage/convert/processor/conversion/ror/ability_subprocessor.py index 0fbb17cb17..89a634bf37 100644 --- a/openage/convert/processor/conversion/ror/ability_subprocessor.py +++ b/openage/convert/processor/conversion/ror/ability_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-branches,too-many-statements,too-many-locals # @@ -65,12 +65,7 @@ def apply_discrete_effect_ability( game_entity_name = name_lookup_dict[head_unit_id][0] ability_name = command_lookup_dict[command_id][0] - - if ranged: - ability_parent = "engine.ability.type.RangedDiscreteEffect" - - else: - ability_parent = "engine.ability.type.ApplyDiscreteEffect" + ability_parent = "engine.ability.type.ApplyDiscreteEffect" if projectile == -1: ability_ref = f"{game_entity_name}.{ability_name}" @@ -244,18 +239,35 @@ def apply_discrete_effect_ability( properties, "engine.ability.Ability") + # Range if ranged: + # Range + property_ref = f"{ability_ref}.Ranged" + property_raw_api_object = RawAPIObject(property_ref, + "Ranged", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Ranged") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + # Min range min_range = current_unit["weapon_range_min"].value - ability_raw_api_object.add_raw_member("min_range", - min_range, - "engine.ability.type.RangedDiscreteEffect") + property_raw_api_object.add_raw_member("min_range", + min_range, + "engine.ability.property.type.Ranged") # Max range max_range = current_unit["weapon_range_max"].value - ability_raw_api_object.add_raw_member("max_range", - max_range, - "engine.ability.type.RangedDiscreteEffect") + property_raw_api_object.add_raw_member("max_range", + max_range, + "engine.ability.property.type.Ranged") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + dataset.nyan_api_objects["engine.ability.property.type.Ranged"]: property_forward_ref + }) # Effects batch_ref = f"{ability_ref}.Batch" @@ -690,6 +702,31 @@ def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> For # Ability properties properties = {} + # Range + property_ref = f"{ability_ref}.Ranged" + property_raw_api_object = RawAPIObject(property_ref, + "Ranged", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Ranged") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + min_range = current_unit["weapon_range_min"].value + property_raw_api_object.add_raw_member("min_range", + min_range, + "engine.ability.property.type.Ranged") + max_range = current_unit["weapon_range_max"].value + property_raw_api_object.add_raw_member("max_range", + max_range, + "engine.ability.property.type.Ranged") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + dataset.nyan_api_objects["engine.ability.property.type.Ranged"]: property_forward_ref + }) + # Animation ability_animation_id = current_unit["attack_sprite_id"].value if ability_animation_id > -1: @@ -796,17 +833,6 @@ def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> For max_projectiles, "engine.ability.type.ShootProjectile") - # Range - min_range = current_unit["weapon_range_min"].value - ability_raw_api_object.add_raw_member("min_range", - min_range, - "engine.ability.type.ShootProjectile") - - max_range = current_unit["weapon_range_max"].value - ability_raw_api_object.add_raw_member("max_range", - max_range, - "engine.ability.type.ShootProjectile") - # Reload time and delay reload_time = current_unit["attack_speed"].value ability_raw_api_object.add_raw_member("reload_time", diff --git a/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py b/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py index 2b00b1ad60..98badf0c66 100644 --- a/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py +++ b/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals,too-many-lines,too-many-statements # pylint: disable=too-few-public-methods,too-many-branches @@ -77,13 +77,70 @@ def shoot_projectile_ability( diff_spawn_delay = diff["frame_delay"] diff_spawn_area_offsets = diff["weapon_offset"] - if any(not isinstance(value, NoDiffMember) for value in (diff_min_range, - diff_max_range, - diff_reload_time, + if any(not isinstance(value, NoDiffMember) for value in (diff_reload_time, diff_spawn_delay, diff_spawn_area_offsets)): data_changed = True + if any(not isinstance(value, NoDiffMember) for value in ( + diff_min_range, + diff_max_range + )): + patch_target_ref = f"{game_entity_name}.{ability_name}.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{ability_name}RangedWrapper" + wrapper_ref = f"{container_obj_ref}.{wrapper_name}" + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + if isinstance(line, GenieBuildingLineGroup): + wrapper_raw_api_object.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper_raw_api_object.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + else: + wrapper_raw_api_object.set_location(ForwardRef(converter_group, container_obj_ref)) + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}{ability_name}Ranged" + nyan_patch_ref = ForwardRef(line, nyan_patch_name) + nyan_patch_location = ForwardRef(converter_group, wrapper_ref) + nyan_patch_raw_api_object = RawAPIObject(nyan_patch_ref, + nyan_patch_name, + dataset.nyan_api_objects, + nyan_patch_location) + nyan_patch_raw_api_object.add_raw_parent("engine.util.patch.NyanPatch") + nyan_patch_raw_api_object.set_patch_target(patch_target_forward_ref) + + if not isinstance(diff_min_range, NoDiffMember): + min_range = diff_min_range.value + nyan_patch_raw_api_object.add_raw_patch_member("min_range", + min_range, + "engine.ability.property.type.Ranged", + MemberOperator.ADD) + + if not isinstance(diff_max_range, NoDiffMember): + max_range = diff_max_range.value + nyan_patch_raw_api_object.add_raw_patch_member("max_range", + max_range, + "engine.ability.property.type.Ranged", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + converter_group.add_raw_api_object(wrapper_raw_api_object) + converter_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(converter_group, wrapper_ref) + patches.append(wrapper_forward_ref) + if not isinstance(diff_animation, NoDiffMember): diff_animation_id = diff_animation.value @@ -167,22 +224,6 @@ def shoot_projectile_ability( nyan_patch_raw_api_object.add_raw_parent("engine.util.patch.NyanPatch") nyan_patch_raw_api_object.set_patch_target(patch_target_forward_ref) - if not isinstance(diff_min_range, NoDiffMember): - min_range = diff_min_range.value - - nyan_patch_raw_api_object.add_raw_patch_member("min_range", - min_range, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_max_range, NoDiffMember): - max_range = diff_max_range.value - - nyan_patch_raw_api_object.add_raw_patch_member("max_range", - max_range, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - if not isinstance(diff_reload_time, NoDiffMember): reload_time = diff_reload_time.value diff --git a/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py b/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py index 07ca29004a..f56edc4167 100644 --- a/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-public-methods,too-many-lines,too-many-locals # pylint: disable=too-many-branches,too-many-statements,too-many-arguments @@ -108,12 +108,7 @@ def apply_discrete_effect_ability( game_entity_name = name_lookup_dict[head_unit_id][0] ability_name = command_lookup_dict[command_id][0] - - if ranged: - ability_parent = "engine.ability.type.RangedDiscreteEffect" - - else: - ability_parent = "engine.ability.type.ApplyDiscreteEffect" + ability_parent = "engine.ability.type.ApplyDiscreteEffect" if projectile == -1: ability_ref = f"{game_entity_name}.{ability_name}" @@ -268,18 +263,35 @@ def apply_discrete_effect_ability( properties, "engine.ability.Ability") + # Range if ranged: + # Range + property_ref = f"{ability_ref}.Ranged" + property_raw_api_object = RawAPIObject(property_ref, + "Ranged", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Ranged") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + # Min range min_range = current_unit["weapon_range_min"].value - ability_raw_api_object.add_raw_member("min_range", - min_range, - "engine.ability.type.RangedDiscreteEffect") + property_raw_api_object.add_raw_member("min_range", + min_range, + "engine.ability.property.type.Ranged") # Max range max_range = current_unit["weapon_range_max"].value - ability_raw_api_object.add_raw_member("max_range", - max_range, - "engine.ability.type.RangedDiscreteEffect") + property_raw_api_object.add_raw_member("max_range", + max_range, + "engine.ability.property.type.Ranged") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + dataset.nyan_api_objects["engine.ability.property.type.Ranged"]: property_forward_ref + }) # Effects batch_ref = f"{ability_ref}.Batch" diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource_subprocessor.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource_subprocessor.py index 8da0ec95ef..55c141a70a 100644 --- a/openage/convert/processor/conversion/swgbcc/upgrade_resource_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals,too-many-lines,too-many-statements,too-many-public-methods # @@ -480,7 +480,7 @@ def heal_range_upgrade( game_entity_name = name_lookup_dict[medic_id][0] - patch_target_ref = f"{game_entity_name}.Heal" + patch_target_ref = f"{game_entity_name}.Heal.Ranged" patch_target_forward_ref = ForwardRef(line, patch_target_ref) # Wrapper @@ -506,7 +506,7 @@ def heal_range_upgrade( nyan_patch_raw_api_object.add_raw_patch_member("max_range", value, - "engine.ability.type.RangedContinuousEffect", + "engine.ability.property.type.Ranged", operator) patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index 0b8d6c87e7..f16f3ff950 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -1,4 +1,4 @@ -# Copyright 2019-2024 the openage authors. See copying.md for legal info. +# Copyright 2019-2025 the openage authors. See copying.md for legal info. # # pylint: disable=line-too-long,too-many-lines,too-many-statements """ @@ -104,6 +104,13 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.ability.property.type.Ranged + parents = [api_objects["engine.ability.property.AbilityProperty"]] + nyan_object = NyanObject("Ranged", parents) + fqon = "engine.ability.property.type.Ranged" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.ability.type.ActiveTransformTo parents = [api_objects["engine.ability.Ability"]] nyan_object = NyanObject("ActiveTransformTo", parents) @@ -370,20 +377,6 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) - # engine.ability.type.RangedContinuousEffect - parents = [api_objects["engine.ability.type.ApplyContinuousEffect"]] - nyan_object = NyanObject("RangedContinuousEffect", parents) - fqon = "engine.ability.type.RangedContinuousEffect" - nyan_object.set_fqon(fqon) - api_objects.update({fqon: nyan_object}) - - # engine.ability.type.RangedDiscreteEffect - parents = [api_objects["engine.ability.type.ApplyDiscreteEffect"]] - nyan_object = NyanObject("RangedDiscreteEffect", parents) - fqon = "engine.ability.type.RangedDiscreteEffect" - nyan_object.set_fqon(fqon) - api_objects.update({fqon: nyan_object}) - # engine.ability.type.RegenerateAttribute parents = [api_objects["engine.ability.Ability"]] nyan_object = NyanObject("RegenerateAttribute", parents) @@ -2630,6 +2623,14 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("lock_pool", member_type, None, None, 0) api_object.add_member(member) + # engine.ability.property.type.Ranged + api_object = api_objects["engine.ability.property.type.Ranged"] + + member = NyanMember("min_range", N_FLOAT, None, None, 0) + api_object.add_member(member) + member = NyanMember("max_range", N_FLOAT, None, None, 0) + api_object.add_member(member) + # engine.ability.type.ActiveTransformTo api_object = api_objects["engine.ability.type.ActiveTransformTo"] @@ -2769,8 +2770,6 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: # engine.ability.type.DetectCloak api_object = api_objects["engine.ability.type.DetectCloak"] - member = NyanMember("range", N_FLOAT, None, None, 0) - api_object.add_member(member) subtype = NyanMemberType(api_objects["engine.util.game_entity_type.GameEntityType"]) elem_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) member_type = NyanMemberType(MemberType.SET, (elem_type,)) @@ -2919,8 +2918,6 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: # engine.ability.type.Herd api_object = api_objects["engine.ability.type.Herd"] - member = NyanMember("range", N_FLOAT, None, None, 0) - api_object.add_member(member) member = NyanMember("strength", N_INT, None, None, 0) api_object.add_member(member) subtype = NyanMemberType(api_objects["engine.util.game_entity_type.GameEntityType"]) @@ -3071,22 +3068,6 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("amount", member_type, None, None, 0) api_object.add_member(member) - # engine.ability.type.RangedContinuousEffect - api_object = api_objects["engine.ability.type.RangedContinuousEffect"] - - member = NyanMember("min_range", N_INT, None, None, 0) - api_object.add_member(member) - member = NyanMember("max_range", N_INT, None, None, 0) - api_object.add_member(member) - - # engine.ability.type.RangedDiscreteEffect - api_object = api_objects["engine.ability.type.RangedDiscreteEffect"] - - member = NyanMember("min_range", N_INT, None, None, 0) - api_object.add_member(member) - member = NyanMember("max_range", N_INT, None, None, 0) - api_object.add_member(member) - # engine.ability.type.RegenerateAttribute api_object = api_objects["engine.ability.type.RegenerateAttribute"] @@ -3189,10 +3170,6 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: api_object.add_member(member) member = NyanMember("max_projectiles", N_INT, None, None, 0) api_object.add_member(member) - member = NyanMember("min_range", N_INT, None, None, 0) - api_object.add_member(member) - member = NyanMember("max_range", N_INT, None, None, 0) - api_object.add_member(member) member = NyanMember("reload_time", N_FLOAT, None, None, 0) api_object.add_member(member) member = NyanMember("spawn_delay", N_FLOAT, None, None, 0) From 79ee9463ed5c1e096a0fd2fd203b193fe6c3b288 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 4 May 2025 17:14:52 +0200 Subject: [PATCH 068/118] convert: Fix pylint complaints. --- .../conversion/aoc/ability_subprocessor.py | 7 ++- .../aoc/upgrade_ability_subprocessor.py | 52 +++++++++---------- .../aoc/upgrade_attribute_subprocessor.py | 1 + .../ror/upgrade_ability_subprocessor.py | 30 +++++------ .../conversion/swgbcc/ability_subprocessor.py | 2 + 5 files changed, 50 insertions(+), 42 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/ability_subprocessor.py b/openage/convert/processor/conversion/aoc/ability_subprocessor.py index cf6f0a01b4..19e0a6692e 100644 --- a/openage/convert/processor/conversion/aoc/ability_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/ability_subprocessor.py @@ -5,7 +5,7 @@ # pylint: disable=invalid-name # # TODO: -# pylint: disable=unused-argument,line-too-long +# pylint: disable=unused-argument,line-too-long,too-many-positional-arguments """ Derives and adds abilities to lines. Subroutine of the @@ -304,6 +304,8 @@ def apply_continuous_effect_ability( }) # Effects + effects = None + allowed_types = None if command_id == 101: # Construct effects = AoCEffectSubprocessor.get_construct_effects(line, ability_ref) @@ -584,6 +586,7 @@ def apply_discrete_effect_ability( line.add_raw_api_object(batch_raw_api_object) + effects = None if command_id == 7: # Attack if projectile != 1: @@ -5644,6 +5647,7 @@ def resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: # The unit uses no gathering command or we don't recognize it continue + container_name = None if line.is_gatherer(): gatherer_unit_id = gatherer.get_id() if gatherer_unit_id not in gather_lookup_dict: @@ -5670,6 +5674,7 @@ def resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: "engine.util.storage.ResourceContainer") # Carry capacity + carry_capacity = None if line.is_gatherer(): carry_capacity = gatherer["resource_capacity"].value diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py b/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py index a1287ba4ac..7fd1cf0e36 100644 --- a/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py @@ -4,7 +4,7 @@ # pylint: disable=too-many-public-methods,too-many-branches,too-many-arguments # # TODO: -# pylint: disable=unused-argument,line-too-long +# pylint: disable=unused-argument,line-too-long,too-many-positional-arguments """ Creates upgrade patches for abilities. @@ -1512,32 +1512,32 @@ def shoot_projectile_ability( ability_name = command_lookup_dict[command_id][0] data_changed = False - if diff: - diff_animation = diff["attack_sprite_id"] - diff_comm_sound = diff["command_sound_id"] - diff_min_projectiles = diff["projectile_min_count"] - diff_max_projectiles = diff["projectile_max_count"] - diff_min_range = diff["weapon_range_min"] - diff_max_range = diff["weapon_range_min"] - diff_reload_time = diff["attack_speed"] - # spawn delay also depends on animation - diff_spawn_delay = diff["frame_delay"] - diff_spawn_area_offsets = diff["weapon_offset"] - diff_spawn_area_width = diff["projectile_spawning_area_width"] - diff_spawn_area_height = diff["projectile_spawning_area_length"] - diff_spawn_area_randomness = diff["projectile_spawning_area_randomness"] - if any(not isinstance(value, NoDiffMember) for value in ( - diff_min_projectiles, - diff_max_projectiles, - diff_reload_time, - diff_spawn_delay, - diff_spawn_area_offsets, - diff_spawn_area_width, - diff_spawn_area_height, - diff_spawn_area_randomness - )): - data_changed = True + diff_animation = diff["attack_sprite_id"] + diff_comm_sound = diff["command_sound_id"] + diff_min_projectiles = diff["projectile_min_count"] + diff_max_projectiles = diff["projectile_max_count"] + diff_min_range = diff["weapon_range_min"] + diff_max_range = diff["weapon_range_min"] + diff_reload_time = diff["attack_speed"] + # spawn delay also depends on animation + diff_spawn_delay = diff["frame_delay"] + diff_spawn_area_offsets = diff["weapon_offset"] + diff_spawn_area_width = diff["projectile_spawning_area_width"] + diff_spawn_area_height = diff["projectile_spawning_area_length"] + diff_spawn_area_randomness = diff["projectile_spawning_area_randomness"] + + if any(not isinstance(value, NoDiffMember) for value in ( + diff_min_projectiles, + diff_max_projectiles, + diff_reload_time, + diff_spawn_delay, + diff_spawn_area_offsets, + diff_spawn_area_width, + diff_spawn_area_height, + diff_spawn_area_randomness + )): + data_changed = True if any(not isinstance(value, NoDiffMember) for value in ( diff_min_range, diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py b/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py index 659d76dfd6..d50f33c8b8 100644 --- a/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py @@ -379,6 +379,7 @@ def ballistics_upgrade( patches = [] + target_mode = None if value == 0: target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.CurrentPosition"] diff --git a/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py b/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py index 98badf0c66..1d943ebd84 100644 --- a/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py +++ b/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py @@ -37,7 +37,7 @@ def shoot_projectile_ability( line: GenieGameEntityGroup, container_obj_ref: str, command_id: int, - diff: ConverterObject = None + diff: ConverterObject ) -> list[ForwardRef]: """ Creates a patch for the Selectable ability of a line. @@ -67,20 +67,20 @@ def shoot_projectile_ability( ability_name = command_lookup_dict[command_id][0] data_changed = False - if diff: - diff_animation = diff["attack_sprite_id"] - diff_comm_sound = diff["command_sound_id"] - diff_min_range = diff["weapon_range_min"] - diff_max_range = diff["weapon_range_min"] - diff_reload_time = diff["attack_speed"] - # spawn delay also depends on animation - diff_spawn_delay = diff["frame_delay"] - diff_spawn_area_offsets = diff["weapon_offset"] - - if any(not isinstance(value, NoDiffMember) for value in (diff_reload_time, - diff_spawn_delay, - diff_spawn_area_offsets)): - data_changed = True + + diff_animation = diff["attack_sprite_id"] + diff_comm_sound = diff["command_sound_id"] + diff_min_range = diff["weapon_range_min"] + diff_max_range = diff["weapon_range_min"] + diff_reload_time = diff["attack_speed"] + # spawn delay also depends on animation + diff_spawn_delay = diff["frame_delay"] + diff_spawn_area_offsets = diff["weapon_offset"] + + if any(not isinstance(value, NoDiffMember) for value in (diff_reload_time, + diff_spawn_delay, + diff_spawn_area_offsets)): + data_changed = True if any(not isinstance(value, NoDiffMember) for value in ( diff_min_range, diff --git a/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py b/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py index f56edc4167..21c859027b 100644 --- a/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py @@ -303,6 +303,7 @@ def apply_discrete_effect_ability( line.add_raw_api_object(batch_raw_api_object) # Effects + effects = [] if command_id == 7: # Attack if projectile != 1: @@ -1517,6 +1518,7 @@ def resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: # The unit uses no gathering command or we don't recognize it continue + container_name = None if line.is_gatherer(): gatherer_unit_id = gatherer.get_id() if gatherer_unit_id not in gather_lookup_dict: From 070a7cc207d22076d3970767fc4fb03b76f83416 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 16:36:31 +0200 Subject: [PATCH 069/118] convert: Add new nyan objects for API 0.5.0. --- .../processor/conversion/aoc/pregen_processor.py | 4 +--- openage/convert/service/read/nyan_api_loader.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index 77ac354938..2b35177a7c 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. +# Copyright 2020-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-locals,too-many-statements # @@ -363,8 +363,6 @@ def generate_activities( wait_raw_api_object.add_raw_member("next", { wait_finish: idle_forward_ref, - # TODO: don't go back to move, go to xor gate that - # branches depending on command wait_command: branch_forward_ref }, xor_event_parent) diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index f16f3ff950..4a66722376 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -560,6 +560,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.TargetInRange + parents = [api_objects["engine.util.activity.condition.Condition"]] + nyan_object = NyanObject("TargetInRange", parents) + fqon = "engine.util.activity.condition.type.TargetInRange" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.util.activity.event.Event parents = [api_objects["engine.root.Object"]] nyan_object = NyanObject("Event", parents) @@ -3313,6 +3320,14 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("next", member_type, None, None, 0) api_object.add_member(member) + # engine.util.activity.condition.type.TargetInRange + api_object = api_objects["engine.util.activity.condition.type.TargetInRange"] + + subtype = NyanMemberType(api_objects["engine.ability.Ability"]) + member_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) + member = NyanMember("ability", member_type, None, None, 0) + api_object.add_member(member) + # engine.util.activity.event.type.Wait api_object = api_objects["engine.util.activity.event.type.Wait"] From 6f3f4e1a777952e4b317937d305d88fa610e61f3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 17:12:48 +0200 Subject: [PATCH 070/118] gamestate: Refactor location of activity gate function definitions. --- libopenage/gamestate/activity/types.h | 73 ++++++++++++++++++- .../gamestate/activity/xor_event_gate.h | 41 +---------- libopenage/gamestate/activity/xor_gate.h | 22 +----- .../gamestate/activity/xor_switch_gate.cpp | 10 +-- .../gamestate/activity/xor_switch_gate.h | 39 +++------- libopenage/gamestate/api/activity.cpp | 4 +- libopenage/gamestate/api/activity.h | 4 +- libopenage/gamestate/api/definitions.h | 2 +- libopenage/gamestate/entity_factory.cpp | 2 +- 9 files changed, 96 insertions(+), 101 deletions(-) diff --git a/libopenage/gamestate/activity/types.h b/libopenage/gamestate/activity/types.h index f9e630171b..04c031552a 100644 --- a/libopenage/gamestate/activity/types.h +++ b/libopenage/gamestate/activity/types.h @@ -1,9 +1,25 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once +#include +#include -namespace openage::gamestate::activity { +#include "time/time.h" + + +namespace openage { + +namespace event { +class Event; +class EventLoop; +} // namespace event + +namespace gamestate { +class GameEntity; +class GameState; + +namespace activity { /** * Node types in the flow graph. @@ -18,4 +34,55 @@ enum class node_t { TASK_SYSTEM, }; -} // namespace openage::gamestate::activity +/** + * Create and register an event on the event loop. + * + * When the event is executed, the control flow continues on the branch + * associated with the event. + * + * @param time Time at which the primer function is executed. + * @param entity Game entity that the activity is assigned to. + * @param loop Event loop that events are registered on. + * @param state Game state. + * @param next_id ID of the next node to visit. This is passed as an event parameter. + * + * @return Event registered on the event loop. + */ +using event_primer_t = std::function(const time::time_t &, + const std::shared_ptr &, + const std::shared_ptr &, + const std::shared_ptr &, + size_t next_id)>; + +/** + * Function that determines if an output node is chosen. + * + * @param time Current simulation time. + * @param entity Entity that is executing the activity. + * + * @return true if the output node is chosen, false otherwise. + */ +using condition_t = std::function &)>; + +/** + * Type used as key for the node lookup dict of the XorSwitchGate. + */ +using switch_key_t = int; + +/** + * Function that retrieves a key for the node lookup dict of the + * XorSwitchGate. The lookup key is calculated from the current state + * of an entity. + * + * @param time Current simulation time. + * @param entity Entity that is executing the activity. + * + * @return Lookup key. + */ +using switch_function_t = std::function &)>; + +} // namespace activity +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/activity/xor_event_gate.h b/libopenage/gamestate/activity/xor_event_gate.h index 64e195faf3..7a3853813a 100644 --- a/libopenage/gamestate/activity/xor_event_gate.h +++ b/libopenage/gamestate/activity/xor_event_gate.h @@ -1,8 +1,7 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once -#include #include #include #include @@ -15,39 +14,7 @@ #include "time/time.h" -namespace openage { -namespace event { -class Event; -class EventLoop; -} // namespace event - -namespace gamestate { -class GameEntity; -class GameState; - -namespace activity { - - -/** - * Create and register an event on the event loop. - * - * When the event is executed, the control flow continues on the branch - * associated with the event. - * - * @param time Time at which the primer function is executed. - * @param entity Game entity that the activity is assigned to. - * @param loop Event loop that events are registered on. - * @param state Game state. - * @param next_id ID of the next node to visit. This is passed as an event parameter. - * - * @return Event registered on the event loop. - */ -using event_primer_t = std::function(const time::time_t &, - const std::shared_ptr &, - const std::shared_ptr &, - const std::shared_ptr &, - size_t next_id)>; - +namespace openage::gamestate::activity { /** * Waits for an event to be executed before continuing the control flow. @@ -107,6 +74,4 @@ class XorEventGate : public Node { std::map primers; }; -} // namespace activity -} // namespace gamestate -} // namespace openage +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/xor_gate.h b/libopenage/gamestate/activity/xor_gate.h index 0ced1d6d27..d00e4157bc 100644 --- a/libopenage/gamestate/activity/xor_gate.h +++ b/libopenage/gamestate/activity/xor_gate.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -16,22 +16,7 @@ #include "time/time.h" -namespace openage::gamestate { -class GameEntity; - -namespace activity { - -/** - * Function that determines if an output node is chosen. - * - * @param time Current simulation time. - * @param entity Entity that is executing the activity. - * - * @return true if the output node is chosen, false otherwise. - */ -using condition_t = std::function &)>; - +namespace openage::gamestate::activity { /** * Chooses one of its output nodes based on conditions. @@ -115,5 +100,4 @@ class XorGate : public Node { std::shared_ptr default_node; }; -} // namespace activity -} // namespace openage::gamestate +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/xor_switch_gate.cpp b/libopenage/gamestate/activity/xor_switch_gate.cpp index 1ae1445e09..3d05c42eae 100644 --- a/libopenage/gamestate/activity/xor_switch_gate.cpp +++ b/libopenage/gamestate/activity/xor_switch_gate.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "xor_switch_gate.h" @@ -11,7 +11,7 @@ XorSwitchGate::XorSwitchGate(node_id_t id, XorSwitchGate::XorSwitchGate(node_id_t id, node_label_t label, - const lookup_function_t &lookup_func, + const switch_function_t &lookup_func, const lookup_dict_t &lookup_dict, const std::shared_ptr &default_node) : Node{id, label}, @@ -20,16 +20,16 @@ XorSwitchGate::XorSwitchGate(node_id_t id, default_node{default_node} {} void XorSwitchGate::set_output(const std::shared_ptr &output, - const lookup_key_t &key) { + const switch_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 { +const switch_function_t &XorSwitchGate::get_lookup_func() const { return this->lookup_func; } -void XorSwitchGate::set_lookup_func(const lookup_function_t &lookup_func) { +void XorSwitchGate::set_lookup_func(const switch_function_t &lookup_func) { this->lookup_func = lookup_func; } diff --git a/libopenage/gamestate/activity/xor_switch_gate.h b/libopenage/gamestate/activity/xor_switch_gate.h index 371c11651e..21139f193c 100644 --- a/libopenage/gamestate/activity/xor_switch_gate.h +++ b/libopenage/gamestate/activity/xor_switch_gate.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -14,10 +14,7 @@ #include "time/time.h" -namespace openage::gamestate { -class GameEntity; - -namespace activity { +namespace openage::gamestate::activity { /** * Chooses one of its output nodes based on enum values. @@ -32,27 +29,10 @@ namespace activity { */ 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>; + using lookup_dict_t = std::unordered_map>; /** * Creates a new XOR switch gate node. @@ -74,7 +54,7 @@ class XorSwitchGate : public Node { */ XorSwitchGate(node_id_t id, node_label_t label, - const lookup_function_t &lookup_func, + const switch_function_t &lookup_func, const lookup_dict_t &lookup_dict, const std::shared_ptr &default_node); @@ -91,21 +71,21 @@ class XorSwitchGate : public Node { * @param key Enumeration value. */ void set_output(const std::shared_ptr &output, - const lookup_key_t &key); + const switch_key_t &key); /** * Get the lookup function for determining the output nodes. * * @return Lookup function. */ - const lookup_function_t &get_lookup_func() const; + const switch_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); + void set_lookup_func(const switch_function_t &lookup_func); /** * Get the lookup dict for the output nodes. @@ -135,7 +115,7 @@ class XorSwitchGate : public Node { /** * Determines the lookup key for the lookup dict from the current state. */ - lookup_function_t lookup_func; + switch_function_t lookup_func; /** * Maps lookup keys to output node IDs. @@ -148,5 +128,4 @@ class XorSwitchGate : public Node { std::shared_ptr default_node; }; -} // namespace activity -} // namespace openage::gamestate +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index 3931ae2638..d3afbfa67b 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -131,7 +131,7 @@ bool APIActivitySwitchCondition::is_switch_condition(const nyan::Object &obj) { return immediate_parent == "engine.util.activity.switch_condition.SwitchCondition"; } -activity::XorSwitchGate::lookup_function_t APIActivitySwitchCondition::get_lookup(const nyan::Object &condition) { +activity::switch_function_t APIActivitySwitchCondition::get_switch_func(const nyan::Object &condition) { nyan::fqon_t immediate_parent = condition.get_parents()[0]; return ACTIVITY_SWITCH_CONDITIONS.get(immediate_parent); } @@ -149,7 +149,7 @@ APIActivitySwitchCondition::lookup_map_t APIActivitySwitchCondition::get_lookup_ auto key_obj = condition.get_view()->get_object(key_value->get_name()); // Get engine lookup key value - auto key = static_cast(COMMAND_DEFS.get(key_obj.get_name())); + auto key = static_cast(COMMAND_DEFS.get(key_obj.get_name())); // Get node ID auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); diff --git a/libopenage/gamestate/api/activity.h b/libopenage/gamestate/api/activity.h index 4413585ec5..f0e1f93475 100644 --- a/libopenage/gamestate/api/activity.h +++ b/libopenage/gamestate/api/activity.h @@ -131,9 +131,9 @@ class APIActivitySwitchCondition { * * @return Lookup function. */ - static activity::XorSwitchGate::lookup_function_t get_lookup(const nyan::Object &condition); + static activity::switch_function_t get_switch_func(const nyan::Object &condition); - using lookup_map_t = std::unordered_map; + using lookup_map_t = std::unordered_map; /** * Get the mapping of lookup keys to output node IDs. Lookup keys are resolved from nyan API diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index faf114f0ea..bd26974a24 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -287,7 +287,7 @@ static const auto ACTIVITY_EVENT_PRIMERS = datastructure::create_const_map( + activity::switch_function_t>( std::pair("engine.util.activity.switch_condition.type.NextCommand", std::function(gamestate::activity::next_command_switch))); diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 4a4b6ac2d0..d4281902fd 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -375,7 +375,7 @@ void EntityFactory::init_activity(const std::shared_ptr("XORSwitchGate.switch"); auto switch_obj = owner_db_view->get_object(switch_value->get_name()); - auto lookup_func = api::APIActivitySwitchCondition::get_lookup(switch_obj); + auto lookup_func = api::APIActivitySwitchCondition::get_switch_func(switch_obj); xor_switch_gate->set_lookup_func(lookup_func); auto lookup_map = api::APIActivitySwitchCondition::get_lookup_map(switch_obj); From 7b025cd789f01cfc129c6a11e997a0d72319c8b6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 21:42:58 +0200 Subject: [PATCH 071/118] gamestate: Allow passing nyan objects in condition/switch functions. --- .../activity/condition/command_in_queue.cpp | 5 +- .../activity/condition/command_in_queue.h | 13 +++-- .../activity/condition/next_command.cpp | 11 ++-- .../activity/condition/next_command.h | 33 ++++++----- .../condition/next_command_switch.cpp | 5 +- .../activity/condition/next_command_switch.h | 19 ++++++- libopenage/gamestate/activity/tests.cpp | 21 ++++--- .../gamestate/activity/tests/node_types.cpp | 56 ++++++++++++------- libopenage/gamestate/activity/types.h | 49 +++++++++++++--- libopenage/gamestate/activity/xor_gate.cpp | 10 ++-- libopenage/gamestate/activity/xor_gate.h | 12 ++-- .../gamestate/activity/xor_switch_gate.cpp | 12 ++-- .../gamestate/activity/xor_switch_gate.h | 25 +++++---- libopenage/gamestate/api/definitions.h | 2 +- libopenage/gamestate/entity_factory.cpp | 25 +++++---- libopenage/gamestate/system/activity.cpp | 12 ++-- 16 files changed, 204 insertions(+), 106 deletions(-) diff --git a/libopenage/gamestate/activity/condition/command_in_queue.cpp b/libopenage/gamestate/activity/condition/command_in_queue.cpp index 7e300701f1..4a47f34086 100644 --- a/libopenage/gamestate/activity/condition/command_in_queue.cpp +++ b/libopenage/gamestate/activity/condition/command_in_queue.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "next_command.h" @@ -9,7 +9,8 @@ namespace openage::gamestate::activity { bool command_in_queue(const time::time_t &time, - const std::shared_ptr &entity) { + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); diff --git a/libopenage/gamestate/activity/condition/command_in_queue.h b/libopenage/gamestate/activity/condition/command_in_queue.h index 67c7a794cc..ab44e83b99 100644 --- a/libopenage/gamestate/activity/condition/command_in_queue.h +++ b/libopenage/gamestate/activity/condition/command_in_queue.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,21 +8,26 @@ #include "time/time.h" +namespace nyan { +class Object; +} + namespace openage::gamestate { class GameEntity; namespace activity { /** - * Condition for command in queue check in the activity system. + * Check whether the entity has a command in its command queue. * * @param time Time when the condition is checked. - * @param entity Game entity. + * @param entity Game entity that the activity is assigned to. * * @return true if there is at least one command in the entity's command queue, false otherwise. */ bool command_in_queue(const time::time_t &time, - const std::shared_ptr &entity); + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */); } // namespace activity } // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/condition/next_command.cpp b/libopenage/gamestate/activity/condition/next_command.cpp index f7fb4b9e24..d030808e91 100644 --- a/libopenage/gamestate/activity/condition/next_command.cpp +++ b/libopenage/gamestate/activity/condition/next_command.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "next_command.h" @@ -9,7 +9,8 @@ namespace openage::gamestate::activity { bool next_command_idle(const time::time_t &time, - const std::shared_ptr &entity) { + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); @@ -22,7 +23,8 @@ bool next_command_idle(const time::time_t &time, } bool next_command_move(const time::time_t &time, - const std::shared_ptr &entity) { + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); @@ -35,7 +37,8 @@ bool next_command_move(const time::time_t &time, } bool next_command_apply_effect(const time::time_t &time, - const std::shared_ptr &entity) { + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); diff --git a/libopenage/gamestate/activity/condition/next_command.h b/libopenage/gamestate/activity/condition/next_command.h index 251cf9e243..5990f1823f 100644 --- a/libopenage/gamestate/activity/condition/next_command.h +++ b/libopenage/gamestate/activity/condition/next_command.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,43 +8,50 @@ #include "time/time.h" +namespace nyan { +class Object; +} + namespace openage::gamestate { class GameEntity; namespace activity { /** - * Condition for next command check in the activity system. + * Check whether the next command in the entity command queue is of type \p IDLE. * * @param time Time when the condition is checked. - * @param entity Game entity. + * @param entity Game entity that the activity is assigned to. * - * @return true if the entity has a idle command next in the queue, false otherwise. + * @return true if the entity has a \p IDLE command next in the queue, false otherwise. */ bool next_command_idle(const time::time_t &time, - const std::shared_ptr &entity); + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */); /** - * Condition for next command check in the activity system. + * Check whether the next command in the entity command queue is of type \p MOVE. * * @param time Time when the condition is checked. - * @param entity Game entity. + * @param entity Game entity that the activity is assigned to. * - * @return true if the entity has a move command next in the queue, false otherwise. + * @return true if the entity has a \p MOVE command next in the queue, false otherwise. */ bool next_command_move(const time::time_t &time, - const std::shared_ptr &entity); + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */); /** - * Condition for next command check in the activity system. + * Check whether the next command in the entity command queue is of type \p APPLY_EFFECT. * * @param time Time when the condition is checked. - * @param entity Game entity. + * @param entity Game entity that the activity is assigned to. * - * @return true if the entity has an apply effect command next in the queue, false otherwise. + * @return true if the entity has an \p APPLY_EFFECT command next in the queue, false otherwise. */ bool next_command_apply_effect(const time::time_t &time, - const std::shared_ptr &entity); + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */); } // namespace activity } // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/condition/next_command_switch.cpp b/libopenage/gamestate/activity/condition/next_command_switch.cpp index be0afd1543..d6e049a5fc 100644 --- a/libopenage/gamestate/activity/condition/next_command_switch.cpp +++ b/libopenage/gamestate/activity/condition/next_command_switch.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "next_command_switch.h" @@ -9,7 +9,8 @@ namespace openage::gamestate::activity { int next_command_switch(const time::time_t &time, - const std::shared_ptr &entity) { + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); diff --git a/libopenage/gamestate/activity/condition/next_command_switch.h b/libopenage/gamestate/activity/condition/next_command_switch.h index ba97d5b322..66176ff90a 100644 --- a/libopenage/gamestate/activity/condition/next_command_switch.h +++ b/libopenage/gamestate/activity/condition/next_command_switch.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,16 +8,29 @@ #include "time/time.h" +namespace nyan { +class Object; +} + namespace openage::gamestate { class GameEntity; namespace activity { /** - * Returns true if the next command in the queue is an idle command. + * Check the type of the next command in the queue and return its associated key + * value. + * + * The key value is the result of a static_cast of the \p command_t enum value. + * + * @param time Time when the condition is checked. + * @param entity Game entity that the activity is assigned to. + * + * @return Key of the output node. */ int next_command_switch(const time::time_t &time, - const std::shared_ptr &entity); + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */); } // namespace activity } // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/tests.cpp b/libopenage/gamestate/activity/tests.cpp index 1ddad0e7e8..1979445068 100644 --- a/libopenage/gamestate/activity/tests.cpp +++ b/libopenage/gamestate/activity/tests.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include #include @@ -154,8 +154,9 @@ const std::shared_ptr activity_flow(const std::shared_ptr(current); auto next_id = node->get_default()->get_id(); for (auto &condition : node->get_conditions()) { - auto condition_func = condition.second; - if (condition_func(0, nullptr)) { + auto condition_obj = condition.second.api_object; + auto condition_func = condition.second.function; + if (condition_func(0, nullptr, condition_obj)) { next_id = condition.first; break; } @@ -233,7 +234,8 @@ void activity_demo() { // Conditional branch static size_t counter = 0; activity::condition_t branch_task1 = [&](const time::time_t & /* time */, - const std::shared_ptr & /* entity */) { + const std::shared_ptr & /* entity */, + const std::shared_ptr & /* api_object */) { log::log(INFO << "Checking condition (counter < 4): counter=" << counter); if (counter < 4) { log::log(INFO << "Selecting path 1 (back to task node " << task1->get_id() << ")"); @@ -243,14 +245,19 @@ void activity_demo() { } return false; }; - xor_node->add_output(task1, branch_task1); + xor_node->add_output(task1, + {nullptr, // API object set to nullptr as it's never used by condition func + branch_task1}); activity::condition_t branch_event = [&](const time::time_t & /* time */, - const std::shared_ptr & /* entity */) { + const std::shared_ptr & /* entity */, + const std::shared_ptr & /* api_object */) { // No check needed here, the event node is always selected log::log(INFO << "Selecting path 2 (to event node " << event_node->get_id() << ")"); return true; }; - xor_node->add_output(event_node, branch_event); + xor_node->add_output(event_node, + {nullptr, // API object set to nullptr as it's never used by condition func + branch_event}); xor_node->set_default(event_node); // event node diff --git a/libopenage/gamestate/activity/tests/node_types.cpp b/libopenage/gamestate/activity/tests/node_types.cpp index 702143b251..0029f2ec8b 100644 --- a/libopenage/gamestate/activity/tests/node_types.cpp +++ b/libopenage/gamestate/activity/tests/node_types.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include @@ -102,7 +102,8 @@ void node_types() { // 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 */) { + auto task_func = [](const time::time_t & /* time */, + const std::shared_ptr & /* entity */) { // Do nothing }; task_custom_node->set_task_func(task_func); @@ -129,25 +130,35 @@ void node_types() { 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; - }); + xor_gate_node->add_output(option1_node, + {nullptr, + [](const time::time_t &time, + const std::shared_ptr & /* entity */, + const std::shared_ptr & /* api_object */) { + 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; - }); + xor_gate_node->add_output(option2_node, + {nullptr, + [](const time::time_t &time, + const std::shared_ptr & /* entity */, + const std::shared_ptr & /* api_object */) { + 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); + // Check if the conditions are set correctly + // we don't pass GameEntity or nyan::Object as they are not used by the condition functions + TESTEQUALS(conditions.at(2).function(time::TIME_ZERO, nullptr, nullptr), true); + TESTEQUALS(conditions.at(3).function(time::TIME_ZERO, nullptr, nullptr), false); - TESTEQUALS(conditions.at(2)(time::TIME_MAX, nullptr), false); - TESTEQUALS(conditions.at(3)(time::TIME_MAX, nullptr), true); + TESTEQUALS(conditions.at(2).function(time::TIME_MAX, nullptr, nullptr), false); + TESTEQUALS(conditions.at(3).function(time::TIME_MAX, nullptr, nullptr), true); // Check if next nodes return correctly TESTEQUALS(xor_gate_node->next(1), default_node); @@ -180,7 +191,9 @@ void node_types() { 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 */) { + auto lookup_func = [](const time::time_t &time, + const std::shared_ptr & /* entity */, + const std::shared_ptr & /* api_object */) { if (time == time::TIME_ZERO) { return 1; } @@ -190,12 +203,17 @@ void node_types() { 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); + // we don't pass a nyan::Object as it's not used by the switch function + xor_switch_gate_node->set_switch_func({nullptr, lookup_func}); + + // Check the switch function + // we don't pass GameEntity as it's not used by the switch functions + auto switch_condition = xor_switch_gate_node->get_switch_func(); + auto switch_obj = switch_condition.api_object; + auto switch_func = switch_condition.function; + TESTEQUALS(switch_func(time::TIME_ZERO, nullptr, switch_obj), 1); + TESTEQUALS(switch_func(time::TIME_MAX, nullptr, switch_obj), 2); + TESTEQUALS(switch_func(time::TIME_MIN, nullptr, switch_obj), 0); auto lookup_dict = xor_switch_gate_node->get_lookup_dict(); diff --git a/libopenage/gamestate/activity/types.h b/libopenage/gamestate/activity/types.h index 04c031552a..1ec1824e94 100644 --- a/libopenage/gamestate/activity/types.h +++ b/libopenage/gamestate/activity/types.h @@ -8,6 +8,10 @@ #include "time/time.h" +namespace nyan { +class Object; +} // namespace nyan + namespace openage { namespace event { @@ -48,10 +52,10 @@ enum class node_t { * * @return Event registered on the event loop. */ -using event_primer_t = std::function(const time::time_t &, - const std::shared_ptr &, - const std::shared_ptr &, - const std::shared_ptr &, +using event_primer_t = std::function(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, size_t next_id)>; /** @@ -59,11 +63,25 @@ using event_primer_t = std::function(cons * * @param time Current simulation time. * @param entity Entity that is executing the activity. + * @param api_object API object that is used to define the condition. May be used to + * store additional data for evaluating the condition. * * @return true if the output node is chosen, false otherwise. */ -using condition_t = std::function &)>; +using condition_t = std::function &entity, + const std::shared_ptr &api_object)>; + +/** + * Condition used to determine if an output node is chosen. + */ +struct condition { + /// API object for the condition definition. + std::shared_ptr api_object; + /// Checks whether the condition is true. + /// TODO: We could look this function up at runtime. + condition_t function; +}; /** * Type used as key for the node lookup dict of the XorSwitchGate. @@ -77,11 +95,26 @@ using switch_key_t = int; * * @param time Current simulation time. * @param entity Entity that is executing the activity. + * @param api_object API object that is used to define the condition. May be used to + * store additional data for evaluating the condition. * * @return Lookup key. */ -using switch_function_t = std::function &)>; +using switch_function_t = std::function &entity, + const std::shared_ptr &api_object)>; + +/** + * Condition used to determine which output node of the + * XorSwitchGate is chosen. + */ +struct switch_condition { + /// API object for the condition definition. + std::shared_ptr api_object; + /// Returns the node lookup key for the output node that is chosen. + /// TODO: We could look this function up at runtime. + switch_function_t function; +}; } // namespace activity } // namespace gamestate diff --git a/libopenage/gamestate/activity/xor_gate.cpp b/libopenage/gamestate/activity/xor_gate.cpp index 5d37908f8b..937d476ef4 100644 --- a/libopenage/gamestate/activity/xor_gate.cpp +++ b/libopenage/gamestate/activity/xor_gate.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "xor_gate.h" @@ -17,7 +17,7 @@ XorGate::XorGate(node_id_t id, XorGate::XorGate(node_id_t id, node_label_t label, const std::vector> &outputs, - const std::vector &conditions, + const std::vector &conditions, const std::shared_ptr &default_node) : Node{id, label, outputs}, conditions{}, @@ -33,12 +33,12 @@ XorGate::XorGate(node_id_t id, } void XorGate::add_output(const std::shared_ptr &output, - const condition_t condition_func) { + const condition condition) { this->outputs.emplace(output->get_id(), output); - this->conditions.emplace(output->get_id(), condition_func); + this->conditions.emplace(output->get_id(), condition); } -const std::map &XorGate::get_conditions() const { +const std::map &XorGate::get_conditions() const { return this->conditions; } diff --git a/libopenage/gamestate/activity/xor_gate.h b/libopenage/gamestate/activity/xor_gate.h index d00e4157bc..89b0816a29 100644 --- a/libopenage/gamestate/activity/xor_gate.h +++ b/libopenage/gamestate/activity/xor_gate.h @@ -44,7 +44,7 @@ class XorGate : public Node { XorGate(node_id_t id, node_label_t label, const std::vector> &outputs, - const std::vector &conditions, + const std::vector &conditions, const std::shared_ptr &default_node); virtual ~XorGate() = default; @@ -57,18 +57,18 @@ class XorGate : public Node { * Add an output node. * * @param output Output node. - * @param condition_func Function that determines whether this output node is chosen. + * @param condition Function that determines whether this output node is chosen. * This must be a valid node ID of one of the output nodes. */ void add_output(const std::shared_ptr &output, - const condition_t condition_func); + const condition condition); /** * Get the output->condition mappings. * * @return Conditions for each output node. */ - const std::map &get_conditions() const; + const std::map &get_conditions() const; /** * Get the default output node. @@ -88,11 +88,11 @@ class XorGate : public Node { private: /** - * Maps output node IDs to condition functions. + * Maps output node IDs to conditions. * * Conditions are checked in order they appear in the map. */ - std::map conditions; + std::map conditions; /** * Default output node. Chosen if no condition is true. diff --git a/libopenage/gamestate/activity/xor_switch_gate.cpp b/libopenage/gamestate/activity/xor_switch_gate.cpp index 3d05c42eae..70658d3e5c 100644 --- a/libopenage/gamestate/activity/xor_switch_gate.cpp +++ b/libopenage/gamestate/activity/xor_switch_gate.cpp @@ -11,11 +11,11 @@ XorSwitchGate::XorSwitchGate(node_id_t id, XorSwitchGate::XorSwitchGate(node_id_t id, node_label_t label, - const switch_function_t &lookup_func, + const switch_condition &switch_func, const lookup_dict_t &lookup_dict, const std::shared_ptr &default_node) : Node{id, label}, - lookup_func{lookup_func}, + switch_func{switch_func}, lookup_dict{lookup_dict}, default_node{default_node} {} @@ -25,12 +25,12 @@ void XorSwitchGate::set_output(const std::shared_ptr &output, this->lookup_dict.emplace(key, output); } -const switch_function_t &XorSwitchGate::get_lookup_func() const { - return this->lookup_func; +const switch_condition &XorSwitchGate::get_switch_func() const { + return this->switch_func; } -void XorSwitchGate::set_lookup_func(const switch_function_t &lookup_func) { - this->lookup_func = lookup_func; +void XorSwitchGate::set_switch_func(const switch_condition &switch_func) { + this->switch_func = switch_func; } const XorSwitchGate::lookup_dict_t &XorSwitchGate::get_lookup_dict() const { diff --git a/libopenage/gamestate/activity/xor_switch_gate.h b/libopenage/gamestate/activity/xor_switch_gate.h index 21139f193c..a4e32beb42 100644 --- a/libopenage/gamestate/activity/xor_switch_gate.h +++ b/libopenage/gamestate/activity/xor_switch_gate.h @@ -48,13 +48,14 @@ class XorSwitchGate : public 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. + * @param switch_func Function for evaluating the key of the output node. + * @param lookup_dict Initial lookup dict that maps switch keys to output node IDs. + * @param default_node Default output node. Chosen if \p switch_func does not + * return a key in the lookup dict. */ XorSwitchGate(node_id_t id, node_label_t label, - const switch_function_t &lookup_func, + const switch_condition &switch_func, const lookup_dict_t &lookup_dict, const std::shared_ptr &default_node); @@ -65,7 +66,7 @@ class XorSwitchGate : public Node { } /** - * Set the output node for a given lookup key. + * Set the output node for a given switch key. * * @param output Output node. * @param key Enumeration value. @@ -74,18 +75,18 @@ class XorSwitchGate : public Node { const switch_key_t &key); /** - * Get the lookup function for determining the output nodes. + * Get the switch function for determining the output nodes. * - * @return Lookup function. + * @return Switch function. */ - const switch_function_t &get_lookup_func() const; + const switch_condition &get_switch_func() const; /** - * Set the lookup function for determining the output nodes. + * Set the switch function for determining the output nodes. * - * @param lookup_func Lookup function. + * @param switch_func Switch function. */ - void set_lookup_func(const switch_function_t &lookup_func); + void set_switch_func(const switch_condition &switch_func); /** * Get the lookup dict for the output nodes. @@ -115,7 +116,7 @@ class XorSwitchGate : public Node { /** * Determines the lookup key for the lookup dict from the current state. */ - switch_function_t lookup_func; + switch_condition switch_func; /** * Maps lookup keys to output node IDs. diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index bd26974a24..0b57de6060 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -284,7 +284,7 @@ static const auto ACTIVITY_EVENT_PRIMERS = datastructure::create_const_map( diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index d4281902fd..46665970c9 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -79,16 +79,19 @@ std::shared_ptr create_test_activity() { // branch 1: check if the entity is moveable activity::condition_t command_branch = [&](const time::time_t & /* time */, - const std::shared_ptr &entity) { + const std::shared_ptr &entity, + const std::shared_ptr & /* api_object */) { return entity->has_component(component::component_t::MOVE); }; - condition_moveable->add_output(condition_command, command_branch); + condition_moveable->add_output(condition_command, + {nullptr, // never used by condition func + command_branch}); // default: if it's not moveable, go straight to the end condition_moveable->set_default(end); // branch 1: check if there is already a command in the queue - condition_command->add_output(move, gamestate::activity::command_in_queue); + condition_command->add_output(move, {nullptr, gamestate::activity::command_in_queue}); // default: if there is no command, wait for a command condition_command->set_default(wait_for_command); @@ -338,13 +341,15 @@ void EntityFactory::init_activity(const std::shared_ptr("XORGate.next"); for (auto &condition : conditions->get()) { auto condition_value = std::dynamic_pointer_cast(condition.get_ptr()); - auto condition_obj = owner_db_view->get_object(condition_value->get_name()); + auto condition_obj = owner_db_view->get_object_ptr(condition_value->get_name()); - auto output_value = condition_obj.get("Condition.next")->get_name(); + auto output_value = condition_obj->get("Condition.next")->get_name(); auto output_id = visited[output_value]; auto output_node = node_id_map[output_id]; - xor_gate->add_output(output_node, api::APIActivityCondition::get_condition(condition_obj)); + // TODO: Replace nullptr with object + xor_gate->add_output(output_node, + {condition_obj, api::APIActivityCondition::get_condition(*condition_obj)}); } auto default_fqon = nyan_node.get("XORGate.default")->get_name(); @@ -373,12 +378,12 @@ void EntityFactory::init_activity(const std::shared_ptr(activity_node); auto switch_value = nyan_node.get("XORSwitchGate.switch"); - auto switch_obj = owner_db_view->get_object(switch_value->get_name()); + auto switch_obj = owner_db_view->get_object_ptr(switch_value->get_name()); - auto lookup_func = api::APIActivitySwitchCondition::get_switch_func(switch_obj); - xor_switch_gate->set_lookup_func(lookup_func); + auto switch_condition_func = api::APIActivitySwitchCondition::get_switch_func(*switch_obj); + xor_switch_gate->set_switch_func({switch_obj, switch_condition_func}); - auto lookup_map = api::APIActivitySwitchCondition::get_lookup_map(switch_obj); + auto lookup_map = api::APIActivitySwitchCondition::get_lookup_map(*switch_obj); for (const auto &[key, node_id] : lookup_map) { auto output_id = visited[node_id]; auto output_node = node_id_map[output_id]; diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index 92819ab556..02443c0554 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "activity.h" @@ -89,8 +89,9 @@ void Activity::advance(const time::time_t &start_time, auto node = std::static_pointer_cast(current_node); auto next_id = node->get_default()->get_id(); for (auto &condition : node->get_conditions()) { - auto condition_func = condition.second; - if (condition_func(start_time, entity)) { + auto condition_obj = condition.second.api_object; + auto condition_func = condition.second.function; + if (condition_func(start_time, entity, condition_obj)) { next_id = condition.first; break; } @@ -116,7 +117,10 @@ void Activity::advance(const time::time_t &start_time, 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); + + auto switch_condition_obj = node->get_switch_func().api_object; + auto switch_condition_func = node->get_switch_func().function; + auto key = switch_condition_func(start_time, entity, switch_condition_obj); if (node->get_lookup_dict().contains(key)) { next_id = node->get_lookup_dict().at(key)->get_id(); } From a69c98c0efe8255548b58622e694cbfc80647edc Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 22:51:20 +0200 Subject: [PATCH 072/118] convert: Change nyan objects for nyan API 0.5.0. Remove separate NextCommand... objects and use single NextCommand object. --- .../convert/service/read/nyan_api_loader.py | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index 4a66722376..e55d2996de 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -539,24 +539,10 @@ 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 + # engine.util.activity.condition.type.NextCommand 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) - fqon = "engine.util.activity.condition.type.NextCommandIdle" - nyan_object.set_fqon(fqon) - api_objects.update({fqon: nyan_object}) - - # engine.util.activity.condition.type.NextCommandMove - parents = [api_objects["engine.util.activity.condition.Condition"]] - nyan_object = NyanObject("NextCommandMove", parents) - fqon = "engine.util.activity.condition.type.NextCommandMove" + nyan_object = NyanObject("NextCommand", parents) + fqon = "engine.util.activity.condition.type.NextCommand" nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) @@ -3320,6 +3306,14 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("next", member_type, None, None, 0) api_object.add_member(member) + # engine.util.activity.condition.type.NextCommand + api_object = api_objects["engine.util.activity.condition.type.NextCommand"] + + subtype = NyanMemberType(api_objects["engine.util.command.Command"]) + member_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) + member = NyanMember("command", member_type, None, None, 0) + api_object.add_member(member) + # engine.util.activity.condition.type.TargetInRange api_object = api_objects["engine.util.activity.condition.type.TargetInRange"] From 81a03c55c70fc422c333ec93eeb1638df8261858 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 23:03:34 +0200 Subject: [PATCH 073/118] gamestate: Consolidate NextCommand condition functions into one. --- .../activity/condition/next_command.cpp | 41 +++++-------------- .../activity/condition/next_command.h | 35 ++++------------ libopenage/gamestate/api/definitions.h | 8 +--- 3 files changed, 19 insertions(+), 65 deletions(-) diff --git a/libopenage/gamestate/activity/condition/next_command.cpp b/libopenage/gamestate/activity/condition/next_command.cpp index d030808e91..df7616943e 100644 --- a/libopenage/gamestate/activity/condition/next_command.cpp +++ b/libopenage/gamestate/activity/condition/next_command.cpp @@ -2,15 +2,18 @@ #include "next_command.h" +#include + +#include "gamestate/api/definitions.h" #include "gamestate/component/internal/command_queue.h" #include "gamestate/game_entity.h" namespace openage::gamestate::activity { -bool next_command_idle(const time::time_t &time, - const std::shared_ptr &entity, - const std::shared_ptr & /* api_object */) { +bool next_command(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr &condition) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); @@ -18,36 +21,12 @@ bool next_command_idle(const time::time_t &time, return false; } - auto command = command_queue->get_queue().front(time); - return command->get_type() == component::command::command_t::MOVE; -} - -bool next_command_move(const time::time_t &time, - const std::shared_ptr &entity, - const std::shared_ptr & /* api_object */) { - auto command_queue = std::dynamic_pointer_cast( - entity->get_component(component::component_t::COMMANDQUEUE)); - - if (command_queue->get_queue().empty(time)) { - return false; - } + auto queue_command = command_queue->get_queue().front(time); - auto command = command_queue->get_queue().front(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, - const std::shared_ptr & /* api_object */) { - auto command_queue = std::dynamic_pointer_cast( - entity->get_component(component::component_t::COMMANDQUEUE)); - - if (command_queue->get_queue().empty(time)) { - return false; - } + auto compare_command = condition->get("NextCommand.command"); + auto compare_type = api::COMMAND_DEFS.get(compare_command->get_name()); - auto command = command_queue->get_queue().front(time); - return command->get_type() == component::command::command_t::APPLY_EFFECT; + return queue_command->get_type() == compare_type; } } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/condition/next_command.h b/libopenage/gamestate/activity/condition/next_command.h index 5990f1823f..edb4f4a25d 100644 --- a/libopenage/gamestate/activity/condition/next_command.h +++ b/libopenage/gamestate/activity/condition/next_command.h @@ -18,40 +18,19 @@ class GameEntity; namespace activity { /** - * Check whether the next command in the entity command queue is of type \p IDLE. + * Check whether the next command in the entity command queue is of a specific type. * - * @param time Time when the condition is checked. - * @param entity Game entity that the activity is assigned to. - * - * @return true if the entity has a \p IDLE command next in the queue, false otherwise. - */ -bool next_command_idle(const time::time_t &time, - const std::shared_ptr &entity, - const std::shared_ptr & /* api_object */); - -/** - * Check whether the next command in the entity command queue is of type \p MOVE. - * - * @param time Time when the condition is checked. - * @param entity Game entity that the activity is assigned to. - * - * @return true if the entity has a \p MOVE command next in the queue, false otherwise. - */ -bool next_command_move(const time::time_t &time, - const std::shared_ptr &entity, - const std::shared_ptr & /* api_object */); - -/** - * Check whether the next command in the entity command queue is of type \p APPLY_EFFECT. + * The command type is parsed from the nyan object \p condition. * * @param time Time when the condition is checked. * @param entity Game entity that the activity is assigned to. + * @param condition nyan object for the condition. Used to read the command type. * - * @return true if the entity has an \p APPLY_EFFECT command next in the queue, false otherwise. + * @return true if the entity has the command next in the queue, false otherwise. */ -bool next_command_apply_effect(const time::time_t &time, - const std::shared_ptr &entity, - const std::shared_ptr & /* api_object */); +bool next_command(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr &condition); } // namespace activity } // namespace openage::gamestate diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index 0b57de6060..a61fe8db0f 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -265,12 +265,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", - std::function(gamestate::activity::next_command_move))); + std::pair("engine.util.activity.condition.type.NextCommand", + std::function(gamestate::activity::next_command))); /** * Maps API activity event types to event primer functions. From 9f79449e75fba9739065a87a447d06ee1a0dcfe3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 02:06:57 +0200 Subject: [PATCH 074/118] gamestate: Store current target of command. --- .../component/internal/command_queue.cpp | 8 ++++-- .../component/internal/command_queue.h | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/component/internal/command_queue.cpp b/libopenage/gamestate/component/internal/command_queue.cpp index 4c6963a75d..05b42bd9f6 100644 --- a/libopenage/gamestate/component/internal/command_queue.cpp +++ b/libopenage/gamestate/component/internal/command_queue.cpp @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #include "command_queue.h" @@ -10,7 +10,8 @@ namespace openage::gamestate::component { CommandQueue::CommandQueue(const std::shared_ptr &loop) : - command_queue{loop, 0} { + command_queue{loop, 0}, + target{loop, 0} { } inline component_t CommandQueue::get_type() const { @@ -30,5 +31,8 @@ const std::shared_ptr CommandQueue::pop_command(const time::ti return this->command_queue.pop_front(time); } +curve::Discrete &CommandQueue::get_target() { + return this->target; +} } // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index fb3179b470..414cb3ed81 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -3,11 +3,15 @@ #pragma once #include +#include +#include "coord/phys.h" #include "curve/container/queue.h" +#include "curve/discrete.h" #include "gamestate/component/internal/commands/base_command.h" #include "gamestate/component/internal_component.h" #include "gamestate/component/types.h" +#include "gamestate/types.h" #include "time/time.h" @@ -55,11 +59,33 @@ class CommandQueue final : public InternalComponent { */ const std::shared_ptr pop_command(const time::time_t &time); + /** + * Target type with several possible representations. + * + * Can be: + * - coord::phys3: Position in the game world. + * - entity_id_t: ID of another entity. + * - std::nullopt: Nothing. + */ + using optional_target_t = std::optional>; + + /** + * Get the targets of the entity over time. + * + * @return Targets over time. + */ + curve::Discrete &get_target(); + private: /** * Command queue. */ curve::Queue> command_queue; + + /** + * Target of the entity. + */ + curve::Discrete target; }; } // namespace gamestate::component From e2ae4069c560b50ccd68f939cedef6b4df0491af Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 02:14:39 +0200 Subject: [PATCH 075/118] gamestate: Allow checking for the Ranged property in nyan API. --- 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 a61fe8db0f..59d4d024e4 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -205,7 +205,9 @@ static const auto ABILITY_PROPERTY_DEFS = datastructure::create_const_map("engine.ability.property.type.Diplomatic"))), std::pair(ability_property_t::LOCK, - nyan::ValueHolder(std::make_shared("engine.ability.property.type.Lock")))); + nyan::ValueHolder(std::make_shared("engine.ability.property.type.Lock"))), + std::pair(ability_property_t::RANGED, + nyan::ValueHolder(std::make_shared("engine.ability.property.type.Ranged")))); /** * Maps internal effect property types to nyan API values. diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index 75095e67d4..032b4b8fe1 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -55,6 +55,7 @@ enum class ability_property_t { EXECUTION_SOUND, DIPLOMATIC, LOCK, + RANGED, }; /** From cc35ed6c3cf445760bedae0f48aa6724f255b3ed Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 02:35:52 +0200 Subject: [PATCH 076/118] gamestate: Remove obsolete ranged apply effect abilities. --- libopenage/gamestate/entity_factory.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 46665970c9..0ec2343a1d 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -215,9 +215,7 @@ 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.RangedContinuousEffect" - or ability_parent == "engine.ability.type.RangedDiscreteEffect") { + or ability_parent == "engine.ability.type.ApplyDiscreteEffect") { auto apply_effect = std::make_shared(loop, ability_obj); entity->add_component(apply_effect); } From d19cfceb98825f525fe89a8261e0187174895a2c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 02:46:42 +0200 Subject: [PATCH 077/118] gamestate: Add reverse lookup for ability fqon to ability type. --- .../activity/condition/next_command.cpp | 2 +- libopenage/gamestate/api/activity.cpp | 18 +++---- libopenage/gamestate/api/definitions.h | 47 ++++++++++++++----- libopenage/gamestate/api/types.h | 2 - 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/libopenage/gamestate/activity/condition/next_command.cpp b/libopenage/gamestate/activity/condition/next_command.cpp index df7616943e..c800e5aae2 100644 --- a/libopenage/gamestate/activity/condition/next_command.cpp +++ b/libopenage/gamestate/activity/condition/next_command.cpp @@ -24,7 +24,7 @@ bool next_command(const time::time_t &time, auto queue_command = command_queue->get_queue().front(time); auto compare_command = condition->get("NextCommand.command"); - auto compare_type = api::COMMAND_DEFS.get(compare_command->get_name()); + auto compare_type = api::COMMAND_LOOKUP.get(compare_command->get_name()); return queue_command->get_type() == compare_type; } diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index d3afbfa67b..dd7e337835 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -27,7 +27,7 @@ bool APIActivityNode::is_node(const nyan::Object &obj) { activity::node_t APIActivityNode::get_type(const nyan::Object &node) { nyan::fqon_t immediate_parent = node.get_parents()[0]; - return ACTIVITY_NODE_DEFS.get(immediate_parent); + return ACTIVITY_NODE_LOOKUP.get(immediate_parent); } std::vector APIActivityNode::get_next(const nyan::Object &node) { @@ -84,7 +84,7 @@ std::vector APIActivityNode::get_next(const nyan::Object &node) { 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); + auto switch_condition_type = ACTIVITY_SWITCH_CONDITION_TYPE_LOOKUP.get(switch_condition_parent); switch (switch_condition_type) { case switch_condition_t::NEXT_COMMAND: { @@ -109,11 +109,11 @@ std::vector APIActivityNode::get_next(const nyan::Object &node) { system::system_id_t APIActivityNode::get_system_id(const nyan::Object &ability_node) { auto ability = ability_node.get("Ability.ability"); - if (not ACTIVITY_TASK_SYSTEM_DEFS.contains(ability->get_name())) [[unlikely]] { + if (not ACTIVITY_TASK_SYSTEM_LOOKUP.contains(ability->get_name())) [[unlikely]] { throw Error(MSG(err) << "Ability '" << ability->get_name() << "' has no associated system defined."); } - return ACTIVITY_TASK_SYSTEM_DEFS.get(ability->get_name()); + return ACTIVITY_TASK_SYSTEM_LOOKUP.get(ability->get_name()); } bool APIActivityCondition::is_condition(const nyan::Object &obj) { @@ -123,7 +123,7 @@ bool APIActivityCondition::is_condition(const nyan::Object &obj) { activity::condition_t APIActivityCondition::get_condition(const nyan::Object &condition) { nyan::fqon_t immediate_parent = condition.get_parents()[0]; - return ACTIVITY_CONDITIONS.get(immediate_parent); + return ACTIVITY_CONDITION_LOOKUP.get(immediate_parent); } bool APIActivitySwitchCondition::is_switch_condition(const nyan::Object &obj) { @@ -133,12 +133,12 @@ bool APIActivitySwitchCondition::is_switch_condition(const nyan::Object &obj) { activity::switch_function_t APIActivitySwitchCondition::get_switch_func(const nyan::Object &condition) { nyan::fqon_t immediate_parent = condition.get_parents()[0]; - return ACTIVITY_SWITCH_CONDITIONS.get(immediate_parent); + return ACTIVITY_SWITCH_CONDITION_LOOKUP.get(immediate_parent); } APIActivitySwitchCondition::lookup_map_t APIActivitySwitchCondition::get_lookup_map(const nyan::Object &condition) { nyan::fqon_t immediate_parent = condition.get_parents()[0]; - auto switch_condition_type = ACTIVITY_SWITCH_CONDITION_TYPES.get(immediate_parent); + auto switch_condition_type = ACTIVITY_SWITCH_CONDITION_TYPE_LOOKUP.get(immediate_parent); switch (switch_condition_type) { case switch_condition_t::NEXT_COMMAND: { @@ -149,7 +149,7 @@ APIActivitySwitchCondition::lookup_map_t APIActivitySwitchCondition::get_lookup_ auto key_obj = condition.get_view()->get_object(key_value->get_name()); // Get engine lookup key value - auto key = static_cast(COMMAND_DEFS.get(key_obj.get_name())); + auto key = static_cast(COMMAND_LOOKUP.get(key_obj.get_name())); // Get node ID auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); @@ -171,7 +171,7 @@ bool APIActivityEvent::is_event(const nyan::Object &obj) { } activity::event_primer_t APIActivityEvent::get_primer(const nyan::Object &event) { - return ACTIVITY_EVENT_PRIMERS.get(event.get_name()); + return ACTIVITY_EVENT_PRIMER_LOOKUP.get(event.get_name()); } } // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index 59d4d024e4..bfa5f5507b 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -42,10 +42,6 @@ static const auto ABILITY_DEFS = datastructure::create_const_map("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, @@ -53,6 +49,31 @@ static const auto ABILITY_DEFS = datastructure::create_const_map("engine.ability.type.Turn")))); +/** + * Maps nyan API ability fqon values to internal ability types. + */ +static const auto ABILITY_TYPE_LOOKUP = datastructure::create_const_map( + std::pair("engine.ability.type.Activity", + ability_t::ACTIVITY), + std::pair("engine.ability.type.ApplyContinuousEffect", + ability_t::APPLY_CONTINUOUS_EFFECT), + std::pair("engine.ability.type.ApplyDiscreteEffect", + ability_t::APPLY_DISCRETE_EFFECT), + std::pair("engine.ability.type.Idle", + ability_t::IDLE), + std::pair("engine.ability.type.Move", + ability_t::MOVE), + std::pair("engine.ability.type.LineOfSight", + ability_t::LINE_OF_SIGHT), + std::pair("engine.ability.type.Live", + ability_t::LIVE), + std::pair("engine.ability.type.Resistance", + ability_t::RESISTANCE), + std::pair("engine.ability.type.Selectable", + ability_t::SELECTABLE), + std::pair("engine.ability.type.Turn", + ability_t::TURN)); + /** * Maps internal effect types to nyan API values. */ @@ -234,7 +255,7 @@ static const auto RESISTANCE_PROPERTY_DEFS = datastructure::create_const_map( +static const auto ACTIVITY_NODE_LOOKUP = datastructure::create_const_map( std::pair("engine.util.activity.node.type.Start", activity::node_t::START), std::pair("engine.util.activity.node.type.End", @@ -253,7 +274,7 @@ static const auto ACTIVITY_NODE_DEFS = datastructure::create_const_map( +static const auto ACTIVITY_TASK_SYSTEM_LOOKUP = datastructure::create_const_map( std::pair("engine.ability.type.ApplyDiscreteEffect", system::system_id_t::APPLY_EFFECT), std::pair("engine.ability.type.Idle", @@ -264,7 +285,7 @@ static const auto ACTIVITY_TASK_SYSTEM_DEFS = datastructure::create_const_map( +static const auto ACTIVITY_CONDITION_LOOKUP = 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.NextCommand", @@ -273,7 +294,7 @@ static const auto ACTIVITY_CONDITIONS = datastructure::create_const_map( +static const auto ACTIVITY_EVENT_PRIMER_LOOKUP = datastructure::create_const_map( std::pair("engine.util.activity.event.type.CommandInQueue", std::function(gamestate::activity::primer_command_in_queue)), std::pair("engine.util.activity.event.type.Wait", @@ -284,16 +305,16 @@ static const auto ACTIVITY_EVENT_PRIMERS = datastructure::create_const_map( +static const auto ACTIVITY_SWITCH_CONDITION_LOOKUP = datastructure::create_const_map( std::pair("engine.util.activity.switch_condition.type.NextCommand", std::function(gamestate::activity::next_command_switch))); /** * Maps API activity switch condition types to nyan API values. */ -static const auto ACTIVITY_SWITCH_CONDITION_TYPES = datastructure::create_const_map( +static const auto ACTIVITY_SWITCH_CONDITION_TYPE_LOOKUP = datastructure::create_const_map( std::pair("engine.util.activity.switch_condition.type.NextCommand", switch_condition_t::NEXT_COMMAND)); @@ -307,7 +328,7 @@ static const auto PATCH_PROPERTY_DEFS = datastructure::create_const_map( +static const auto COMMAND_LOOKUP = datastructure::create_const_map( std::pair("engine.util.command.type.Idle", component::command::command_t::IDLE), std::pair("engine.util.command.type.Move", diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index 032b4b8fe1..dfd30b3588 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -16,8 +16,6 @@ enum class ability_t { LINE_OF_SIGHT, LIVE, MOVE, - RANGED_CONTINUOUS_EFFECT, - RANGED_DISCRETE_EFFECT, RESISTANCE, SELECTABLE, TURN, From 664fba2aa6a18896b8c8ef9eba1fb351fc4cdab5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 03:04:15 +0200 Subject: [PATCH 078/118] gamestate: Use switch statement for component assignment to new entity. --- libopenage/gamestate/api/ability.cpp | 13 ++++++++- libopenage/gamestate/api/ability.h | 11 +++++++- libopenage/gamestate/api/types.h | 5 +++- libopenage/gamestate/entity_factory.cpp | 36 +++++++++++++++++-------- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/libopenage/gamestate/api/ability.cpp b/libopenage/gamestate/api/ability.cpp index 1fc24c064b..45124ea540 100644 --- a/libopenage/gamestate/api/ability.cpp +++ b/libopenage/gamestate/api/ability.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "ability.h" @@ -36,6 +36,17 @@ bool APIAbility::check_type(const nyan::Object &ability, return ability_val->get_name() == immediate_parent; } +ability_t APIAbility::get_type(const nyan::Object &ability) { + nyan::fqon_t immediate_parent = ability.get_parents()[0]; + + // TODO: remove once other ability types are implemented + if (not ABILITY_TYPE_LOOKUP.contains(immediate_parent)) { + return ability_t::UNKNOWN; + } + + return ABILITY_TYPE_LOOKUP.get(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); diff --git a/libopenage/gamestate/api/ability.h b/libopenage/gamestate/api/ability.h index 1df65a95fd..f3afaffb1e 100644 --- a/libopenage/gamestate/api/ability.h +++ b/libopenage/gamestate/api/ability.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -33,6 +33,15 @@ class APIAbility { static bool check_type(const nyan::Object &ability, const ability_t &type); + /** + * Get the internal ability type from a nyan ability. + * + * @param ability \p Ability nyan object (type == \p engine.ability.Ability). + * + * @return Internal ability type. + */ + static ability_t get_type(const nyan::Object &ability); + /** * Check if an ability has a given property. * diff --git a/libopenage/gamestate/api/types.h b/libopenage/gamestate/api/types.h index dfd30b3588..35b036dd69 100644 --- a/libopenage/gamestate/api/types.h +++ b/libopenage/gamestate/api/types.h @@ -20,7 +20,10 @@ enum class ability_t { SELECTABLE, TURN, - // TODO + // TODO: other ability types + + // TODO: remove once other ability types are implemented + UNKNOWN, }; /** diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 0ec2343a1d..33cf5bf847 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -23,6 +23,7 @@ #include "gamestate/activity/xor_event_gate.h" #include "gamestate/activity/xor_gate.h" #include "gamestate/activity/xor_switch_gate.h" +#include "gamestate/api/ability.h" #include "gamestate/api/activity.h" #include "gamestate/component/api/apply_effect.h" #include "gamestate/component/api/idle.h" @@ -174,19 +175,24 @@ void EntityFactory::init_components(const std::shared_ptrget_object(ability_fqon); auto ability_parent = ability_obj.get_parents()[0]; - if (ability_parent == "engine.ability.type.Move") { + auto ability_type = api::APIAbility::get_type(ability_obj); + switch (ability_type) { + case api::ability_t::MOVE: { auto move = std::make_shared(loop, ability_obj); entity->add_component(move); + break; } - else if (ability_parent == "engine.ability.type.Turn") { + case api::ability_t::TURN: { auto turn = std::make_shared(loop, ability_obj); entity->add_component(turn); + break; } - else if (ability_parent == "engine.ability.type.Idle") { + case api::ability_t::IDLE: { auto idle = std::make_shared(loop, ability_obj); entity->add_component(idle); + break; } - else if (ability_parent == "engine.ability.type.Live") { + case api::ability_t::LIVE: { auto live = std::make_shared(loop, ability_obj); entity->add_component(live); @@ -206,28 +212,36 @@ void EntityFactory::init_components(const std::shared_ptr(loop, ability_obj); entity->add_component(selectable); + break; } - else if (ability_parent == "engine.ability.type.ApplyContinuousEffect" - or ability_parent == "engine.ability.type.ApplyDiscreteEffect") { + case api::ability_t::APPLY_DISCRETE_EFFECT: + [[fallthrough]]; + case api::ability_t::APPLY_CONTINUOUS_EFFECT: { auto apply_effect = std::make_shared(loop, ability_obj); entity->add_component(apply_effect); + break; } - else if (ability_parent == "engine.ability.type.Resistance") { + case api::ability_t::RESISTANCE: { auto resistance = std::make_shared(loop, ability_obj); entity->add_component(resistance); + break; } - else if (ability_parent == "engine.ability.type.LineOfSight") { + case api::ability_t::LINE_OF_SIGHT: { auto line_of_sight = std::make_shared(loop, ability_obj); entity->add_component(line_of_sight); + break; } - else { + default: + // TODO: Change verbosity from SPAM to INFO once we cover all ability types log::log(SPAM << "Entity has unrecognized ability type: " << ability_parent); } } From ea2393ba6bd43bc6c0cd9b0986042f1ebea4c3e7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 04:35:23 +0200 Subject: [PATCH 079/118] gamestate: Pass state to condition functions. --- .../activity/condition/command_in_queue.cpp | 1 + .../activity/condition/command_in_queue.h | 2 ++ .../activity/condition/next_command.cpp | 1 + .../activity/condition/next_command.h | 2 ++ .../condition/next_command_switch.cpp | 1 + .../activity/condition/next_command_switch.h | 2 ++ libopenage/gamestate/activity/tests.cpp | 4 +++- .../gamestate/activity/tests/node_types.cpp | 21 +++++++++++-------- libopenage/gamestate/activity/types.h | 2 ++ libopenage/gamestate/entity_factory.cpp | 1 + libopenage/gamestate/system/activity.cpp | 4 ++-- 11 files changed, 29 insertions(+), 12 deletions(-) diff --git a/libopenage/gamestate/activity/condition/command_in_queue.cpp b/libopenage/gamestate/activity/condition/command_in_queue.cpp index 4a47f34086..299ca1870a 100644 --- a/libopenage/gamestate/activity/condition/command_in_queue.cpp +++ b/libopenage/gamestate/activity/condition/command_in_queue.cpp @@ -10,6 +10,7 @@ namespace openage::gamestate::activity { bool command_in_queue(const time::time_t &time, const std::shared_ptr &entity, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); diff --git a/libopenage/gamestate/activity/condition/command_in_queue.h b/libopenage/gamestate/activity/condition/command_in_queue.h index ab44e83b99..c55eec6b55 100644 --- a/libopenage/gamestate/activity/condition/command_in_queue.h +++ b/libopenage/gamestate/activity/condition/command_in_queue.h @@ -14,6 +14,7 @@ class Object; namespace openage::gamestate { class GameEntity; +class GameState; namespace activity { @@ -27,6 +28,7 @@ namespace activity { */ bool command_in_queue(const time::time_t &time, const std::shared_ptr &entity, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */); } // namespace activity diff --git a/libopenage/gamestate/activity/condition/next_command.cpp b/libopenage/gamestate/activity/condition/next_command.cpp index c800e5aae2..ff7d965603 100644 --- a/libopenage/gamestate/activity/condition/next_command.cpp +++ b/libopenage/gamestate/activity/condition/next_command.cpp @@ -13,6 +13,7 @@ namespace openage::gamestate::activity { bool next_command(const time::time_t &time, const std::shared_ptr &entity, + const std::shared_ptr & /* state */, const std::shared_ptr &condition) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); diff --git a/libopenage/gamestate/activity/condition/next_command.h b/libopenage/gamestate/activity/condition/next_command.h index edb4f4a25d..a1958a9cfe 100644 --- a/libopenage/gamestate/activity/condition/next_command.h +++ b/libopenage/gamestate/activity/condition/next_command.h @@ -14,6 +14,7 @@ class Object; namespace openage::gamestate { class GameEntity; +class GameState; namespace activity { @@ -30,6 +31,7 @@ namespace activity { */ bool next_command(const time::time_t &time, const std::shared_ptr &entity, + const std::shared_ptr & /* state */, const std::shared_ptr &condition); } // namespace activity diff --git a/libopenage/gamestate/activity/condition/next_command_switch.cpp b/libopenage/gamestate/activity/condition/next_command_switch.cpp index d6e049a5fc..7133f6381a 100644 --- a/libopenage/gamestate/activity/condition/next_command_switch.cpp +++ b/libopenage/gamestate/activity/condition/next_command_switch.cpp @@ -10,6 +10,7 @@ namespace openage::gamestate::activity { int next_command_switch(const time::time_t &time, const std::shared_ptr &entity, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); diff --git a/libopenage/gamestate/activity/condition/next_command_switch.h b/libopenage/gamestate/activity/condition/next_command_switch.h index 66176ff90a..3e49d71c79 100644 --- a/libopenage/gamestate/activity/condition/next_command_switch.h +++ b/libopenage/gamestate/activity/condition/next_command_switch.h @@ -14,6 +14,7 @@ class Object; namespace openage::gamestate { class GameEntity; +class GameState; namespace activity { @@ -30,6 +31,7 @@ namespace activity { */ int next_command_switch(const time::time_t &time, const std::shared_ptr &entity, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */); } // namespace activity diff --git a/libopenage/gamestate/activity/tests.cpp b/libopenage/gamestate/activity/tests.cpp index 1979445068..ee9b193b6c 100644 --- a/libopenage/gamestate/activity/tests.cpp +++ b/libopenage/gamestate/activity/tests.cpp @@ -156,7 +156,7 @@ const std::shared_ptr activity_flow(const std::shared_ptrget_conditions()) { auto condition_obj = condition.second.api_object; auto condition_func = condition.second.function; - if (condition_func(0, nullptr, condition_obj)) { + if (condition_func(0, nullptr, nullptr, condition_obj)) { next_id = condition.first; break; } @@ -235,6 +235,7 @@ void activity_demo() { static size_t counter = 0; activity::condition_t branch_task1 = [&](const time::time_t & /* time */, const std::shared_ptr & /* entity */, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */) { log::log(INFO << "Checking condition (counter < 4): counter=" << counter); if (counter < 4) { @@ -250,6 +251,7 @@ void activity_demo() { branch_task1}); activity::condition_t branch_event = [&](const time::time_t & /* time */, const std::shared_ptr & /* entity */, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */) { // No check needed here, the event node is always selected log::log(INFO << "Selecting path 2 (to event node " << event_node->get_id() << ")"); diff --git a/libopenage/gamestate/activity/tests/node_types.cpp b/libopenage/gamestate/activity/tests/node_types.cpp index 0029f2ec8b..d8237a6d9a 100644 --- a/libopenage/gamestate/activity/tests/node_types.cpp +++ b/libopenage/gamestate/activity/tests/node_types.cpp @@ -134,6 +134,7 @@ void node_types() { {nullptr, [](const time::time_t &time, const std::shared_ptr & /* entity */, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */) { return time == time::TIME_ZERO; }}); @@ -143,6 +144,7 @@ void node_types() { {nullptr, [](const time::time_t &time, const std::shared_ptr & /* entity */, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */) { return time == time::TIME_MAX; }}); @@ -153,12 +155,12 @@ void node_types() { TESTEQUALS(conditions.size(), 2); // Check if the conditions are set correctly - // we don't pass GameEntity or nyan::Object as they are not used by the condition functions - TESTEQUALS(conditions.at(2).function(time::TIME_ZERO, nullptr, nullptr), true); - TESTEQUALS(conditions.at(3).function(time::TIME_ZERO, nullptr, nullptr), false); + // we don't pass GameEntity, GameState, or nyan::Object as they are not used by the condition functions + TESTEQUALS(conditions.at(2).function(time::TIME_ZERO, nullptr, nullptr, nullptr), true); + TESTEQUALS(conditions.at(3).function(time::TIME_ZERO, nullptr, nullptr, nullptr), false); - TESTEQUALS(conditions.at(2).function(time::TIME_MAX, nullptr, nullptr), false); - TESTEQUALS(conditions.at(3).function(time::TIME_MAX, nullptr, nullptr), true); + TESTEQUALS(conditions.at(2).function(time::TIME_MAX, nullptr, nullptr, nullptr), false); + TESTEQUALS(conditions.at(3).function(time::TIME_MAX, nullptr, nullptr, nullptr), true); // Check if next nodes return correctly TESTEQUALS(xor_gate_node->next(1), default_node); @@ -193,6 +195,7 @@ void node_types() { auto lookup_func = [](const time::time_t &time, const std::shared_ptr & /* entity */, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */) { if (time == time::TIME_ZERO) { return 1; @@ -207,13 +210,13 @@ void node_types() { xor_switch_gate_node->set_switch_func({nullptr, lookup_func}); // Check the switch function - // we don't pass GameEntity as it's not used by the switch functions + // we don't pass GameEntity or GameState as it's not used by the switch functions auto switch_condition = xor_switch_gate_node->get_switch_func(); auto switch_obj = switch_condition.api_object; auto switch_func = switch_condition.function; - TESTEQUALS(switch_func(time::TIME_ZERO, nullptr, switch_obj), 1); - TESTEQUALS(switch_func(time::TIME_MAX, nullptr, switch_obj), 2); - TESTEQUALS(switch_func(time::TIME_MIN, nullptr, switch_obj), 0); + TESTEQUALS(switch_func(time::TIME_ZERO, nullptr, nullptr, switch_obj), 1); + TESTEQUALS(switch_func(time::TIME_MAX, nullptr, nullptr, switch_obj), 2); + TESTEQUALS(switch_func(time::TIME_MIN, nullptr, nullptr, switch_obj), 0); auto lookup_dict = xor_switch_gate_node->get_lookup_dict(); diff --git a/libopenage/gamestate/activity/types.h b/libopenage/gamestate/activity/types.h index 1ec1824e94..41f24dc857 100644 --- a/libopenage/gamestate/activity/types.h +++ b/libopenage/gamestate/activity/types.h @@ -70,6 +70,7 @@ using event_primer_t = std::function(cons */ using condition_t = std::function &entity, + const std::shared_ptr &state, const std::shared_ptr &api_object)>; /** @@ -102,6 +103,7 @@ using switch_key_t = int; */ using switch_function_t = std::function &entity, + const std::shared_ptr &state, const std::shared_ptr &api_object)>; /** diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 33cf5bf847..8f3da4d24f 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -81,6 +81,7 @@ std::shared_ptr create_test_activity() { // branch 1: check if the entity is moveable activity::condition_t command_branch = [&](const time::time_t & /* time */, const std::shared_ptr &entity, + const std::shared_ptr & /* state */, const std::shared_ptr & /* api_object */) { return entity->has_component(component::component_t::MOVE); }; diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index 02443c0554..072da8bab1 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.cpp @@ -91,7 +91,7 @@ void Activity::advance(const time::time_t &start_time, for (auto &condition : node->get_conditions()) { auto condition_obj = condition.second.api_object; auto condition_func = condition.second.function; - if (condition_func(start_time, entity, condition_obj)) { + if (condition_func(start_time, entity, state, condition_obj)) { next_id = condition.first; break; } @@ -120,7 +120,7 @@ void Activity::advance(const time::time_t &start_time, auto switch_condition_obj = node->get_switch_func().api_object; auto switch_condition_func = node->get_switch_func().function; - auto key = switch_condition_func(start_time, entity, switch_condition_obj); + auto key = switch_condition_func(start_time, entity, state, switch_condition_obj); if (node->get_lookup_dict().contains(key)) { next_id = node->get_lookup_dict().at(key)->get_id(); } From 0b7f0727c958a30155db623648b708f09ca5e3f6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 04:41:08 +0200 Subject: [PATCH 080/118] gamestate; Define initial game entity IDs as constants. --- libopenage/gamestate/definitions.h | 18 +++++++++++++++++- libopenage/gamestate/entity_factory.h | 9 +++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/libopenage/gamestate/definitions.h b/libopenage/gamestate/definitions.h index a2d3f113d4..8e514571cb 100644 --- a/libopenage/gamestate/definitions.h +++ b/libopenage/gamestate/definitions.h @@ -1,8 +1,10 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once #include "coord/phys.h" +#include "gamestate/types.h" + /** * Hardcoded definitions for parameters used in the gamestate. @@ -16,4 +18,18 @@ namespace openage::gamestate { */ constexpr coord::phys3 WORLD_ORIGIN = coord::phys3{0, 0, 0}; +/** + * Starting point for entity IDs. + * + * IDs 0-99 are reserved. + */ +constexpr entity_id_t GAME_ENTITY_ID_START = 100; + +/** + * Starting point for player IDs. + * + * ID 0 is reserved. + */ +constexpr player_id_t PLAYER_ID_START = 1; + } // namespace openage::gamestate diff --git a/libopenage/gamestate/entity_factory.h b/libopenage/gamestate/entity_factory.h index 7ac3f4a7f1..f3f4e5ab1c 100644 --- a/libopenage/gamestate/entity_factory.h +++ b/libopenage/gamestate/entity_factory.h @@ -7,6 +7,7 @@ #include +#include "gamestate/definitions.h" #include "gamestate/types.h" @@ -118,17 +119,13 @@ class EntityFactory { /** * ID of the next game entity to be created. - * - * IDs 0-99 are reserved. */ - entity_id_t next_entity_id = 100; + entity_id_t next_entity_id = GAME_ENTITY_ID_START; /** * ID of the next player to be created. - * - * ID 0 is reserved. */ - player_id_t next_player_id = 1; + player_id_t next_player_id = PLAYER_ID_START; /** * Factory for creating connector objects to the renderer which make game entities displayable. From 0572fa190ea9c7b2ff416badfbfba041bb849234 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 04:51:28 +0200 Subject: [PATCH 081/118] gamestate: Add lookup for ability type to component type. --- libopenage/gamestate/api/ability.cpp | 6 ++++++ libopenage/gamestate/api/ability.h | 11 +++++++++++ libopenage/gamestate/api/definitions.h | 16 ++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/libopenage/gamestate/api/ability.cpp b/libopenage/gamestate/api/ability.cpp index 45124ea540..fd1bed4516 100644 --- a/libopenage/gamestate/api/ability.cpp +++ b/libopenage/gamestate/api/ability.cpp @@ -47,6 +47,12 @@ ability_t APIAbility::get_type(const nyan::Object &ability) { return ABILITY_TYPE_LOOKUP.get(immediate_parent); } +component::component_t APIAbility::get_component_type(const nyan::Object &ability) { + auto ability_type = APIAbility::get_type(ability); + + return COMPONENT_TYPE_LOOKUP.get(ability_type); +} + 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); diff --git a/libopenage/gamestate/api/ability.h b/libopenage/gamestate/api/ability.h index f3afaffb1e..f58974385e 100644 --- a/libopenage/gamestate/api/ability.h +++ b/libopenage/gamestate/api/ability.h @@ -5,6 +5,8 @@ #include #include "gamestate/api/types.h" +#include "gamestate/component/types.h" + namespace openage::gamestate::api { @@ -42,6 +44,15 @@ class APIAbility { */ static ability_t get_type(const nyan::Object &ability); + /** + * Get the internal component type from a nyan ability. + * + * @param ability \p Ability nyan object (type == \p engine.ability.Ability). + * + * @return Internal component type. + */ + static component::component_t get_component_type(const nyan::Object &ability); + /** * Check if an ability has a given property. * diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index bfa5f5507b..5081539eab 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -19,6 +19,7 @@ #include "gamestate/activity/xor_switch_gate.h" #include "gamestate/api/types.h" #include "gamestate/component/internal/commands/types.h" +#include "gamestate/component/types.h" #include "gamestate/system/types.h" @@ -74,6 +75,21 @@ static const auto ABILITY_TYPE_LOOKUP = datastructure::create_const_map( + std::pair(ability_t::ACTIVITY, component::component_t::ACTIVITY), + std::pair(ability_t::APPLY_CONTINUOUS_EFFECT, component::component_t::APPLY_EFFECT), + std::pair(ability_t::APPLY_DISCRETE_EFFECT, component::component_t::APPLY_EFFECT), + std::pair(ability_t::IDLE, component::component_t::IDLE), + std::pair(ability_t::MOVE, component::component_t::MOVE), + std::pair(ability_t::LINE_OF_SIGHT, component::component_t::LINE_OF_SIGHT), + std::pair(ability_t::LIVE, component::component_t::LIVE), + std::pair(ability_t::RESISTANCE, component::component_t::RESISTANCE), + std::pair(ability_t::SELECTABLE, component::component_t::SELECTABLE), + std::pair(ability_t::TURN, component::component_t::TURN)); + /** * Maps internal effect types to nyan API values. */ From c16b0a4b6f271390bd4f41206c5a882f2f36915e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 04:52:06 +0200 Subject: [PATCH 082/118] gamestate: Add condition for checking if a target is in range of an ability. --- .../activity/condition/CMakeLists.txt | 1 + .../activity/condition/target_in_range.cpp | 72 +++++++++++++++++++ .../activity/condition/target_in_range.h | 44 ++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 libopenage/gamestate/activity/condition/target_in_range.cpp create mode 100644 libopenage/gamestate/activity/condition/target_in_range.h diff --git a/libopenage/gamestate/activity/condition/CMakeLists.txt b/libopenage/gamestate/activity/condition/CMakeLists.txt index c4ba029b27..fc61f4a363 100644 --- a/libopenage/gamestate/activity/condition/CMakeLists.txt +++ b/libopenage/gamestate/activity/condition/CMakeLists.txt @@ -2,4 +2,5 @@ add_sources(libopenage command_in_queue.cpp next_command_switch.cpp next_command.cpp + target_in_range.cpp ) diff --git a/libopenage/gamestate/activity/condition/target_in_range.cpp b/libopenage/gamestate/activity/condition/target_in_range.cpp new file mode 100644 index 0000000000..2308072779 --- /dev/null +++ b/libopenage/gamestate/activity/condition/target_in_range.cpp @@ -0,0 +1,72 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#include "target_in_range.h" + +#include + +#include "gamestate/api/ability.h" +#include "gamestate/component/internal/command_queue.h" +#include "gamestate/component/internal/position.h" +#include "gamestate/component/types.h" +#include "gamestate/game_entity.h" +#include "gamestate/game_state.h" + + +namespace openage::gamestate::activity { + +bool target_in_range(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr &state, + const std::shared_ptr &condition) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + auto target = command_queue->get_target().get(time); + + if (not target.has_value()) { + // No target exists, exit early + return false; + } + + auto ability_obj = condition->get("NextCommand.ability"); + auto component_type = api::APIAbility::get_component_type(*ability_obj); + if (not entity->has_component(component_type)) { + // Entity does not have the component matching the ability, exit early + return false; + } + + // Fetch min/max range from the ability + nyan::value_float_t min_range = 0; + nyan::value_float_t max_range = 0; + if (api::APIAbility::check_property(*ability_obj, api::ability_property_t::RANGED)) { + // Get min/max range from the property of the ability + auto range_obj = api::APIAbility::get_property(*ability_obj, api::ability_property_t::RANGED); + min_range = range_obj.get_float("Ranged.min_range"); + max_range = range_obj.get_float("Ranged.max_range"); + } + + // Fetch the current position of the entity + auto position = std::dynamic_pointer_cast( + entity->get_component(component::component_t::POSITION)); + auto current_pos = position->get_positions().get(time); + + if (std::holds_alternative(target.value())) { + // Target is a position + auto target_pos = std::get(target.value()); + auto distance = (target_pos - current_pos).length(); + + return (distance >= min_range and distance <= max_range); + } + + // Target is a game entity + auto target_entity_id = std::get(target.value()); + auto target_entity = state->get_game_entity(target_entity_id); + + auto target_position = std::dynamic_pointer_cast( + target_entity->get_component(component::component_t::POSITION)); + auto target_pos = target_position->get_positions().get(time); + auto distance = (target_pos - current_pos).length(); + + return (distance >= min_range and distance <= max_range); +} + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/condition/target_in_range.h b/libopenage/gamestate/activity/condition/target_in_range.h new file mode 100644 index 0000000000..3b3562c4bf --- /dev/null +++ b/libopenage/gamestate/activity/condition/target_in_range.h @@ -0,0 +1,44 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace nyan { +class Object; +} + +namespace openage::gamestate { +class GameEntity; +class GameState; + +namespace activity { + +/** + * Check whether the current target of the game entity is within range of a specific ability. + * + * The ability type is parsed from the nyan object \p api_object. + * + * For the check to pass, the following subconditions must be met: + * - \p entity has a target. + * - \p entity has a matching component for the ability type. + * - the distance between \p entity position and the target position is less than + * or equal to the range of the ability. + * + * @param time Time when the condition is checked. + * @param entity Game entity that the activity is assigned to. + * @param api_object nyan object for the condition. Used to read the command type. + * + * @return true if the target is within range of the ability, false otherwise. + */ +bool target_in_range(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr &state, + const std::shared_ptr &api_object); + +} // namespace activity +} // namespace openage::gamestate From 6e8318f3db065fd753d3a346b6b645bf8da1b652 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 18:04:57 +0200 Subject: [PATCH 083/118] gamestate: Store target of entity when receiving events. --- .../component/internal/command_queue.cpp | 12 +++++++ .../component/internal/command_queue.h | 32 +++++++++++++++++++ libopenage/gamestate/event/send_command.cpp | 25 ++++++++------- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/libopenage/gamestate/component/internal/command_queue.cpp b/libopenage/gamestate/component/internal/command_queue.cpp index 05b42bd9f6..5832b938c4 100644 --- a/libopenage/gamestate/component/internal/command_queue.cpp +++ b/libopenage/gamestate/component/internal/command_queue.cpp @@ -35,4 +35,16 @@ curve::Discrete &CommandQueue::get_target() { return this->target; } +void CommandQueue::set_target(const time::time_t &time, const coord::phys3 &target) { + this->target.set_last(time, target); +} + +void CommandQueue::set_target(const time::time_t &time, const entity_id_t target) { + this->target.set_last(time, target); +} + +void CommandQueue::clear_target(const time::time_t &time) { + this->target.set_last(time, std::nullopt); +} + } // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index 414cb3ed81..13b9f1ef4f 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -76,6 +76,35 @@ class CommandQueue final : public InternalComponent { */ curve::Discrete &get_target(); + /** + * Set the target of the entity to a position in the game world. + * + * All target after \p time are deleted. + * + * @param time Time at which the target is set. + * @param target Target position in the game world. + */ + void set_target(const time::time_t &time, const coord::phys3 &target); + + /** + * Set the target of the entity to another entity. + * + * All targets after \p time are deleted. + * + * @param time Time at which the target is set. + * @param target Target entity ID. + */ + void set_target(const time::time_t &time, const entity_id_t target); + + /** + * Set the target of the entity to nothing. + * + * All targets after \p time are deleted. + * + * @param time Time at which the target is cleared. + */ + void clear_target(const time::time_t &time); + private: /** * Command queue. @@ -84,6 +113,9 @@ class CommandQueue final : public InternalComponent { /** * Target of the entity. + * + * TODO: We could also figure out the target via the commands. Then we + * don't need to store the target separately. */ curve::Discrete target; }; diff --git a/libopenage/gamestate/event/send_command.cpp b/libopenage/gamestate/event/send_command.cpp index 1a2cb20d43..9fb864d651 100644 --- a/libopenage/gamestate/event/send_command.cpp +++ b/libopenage/gamestate/event/send_command.cpp @@ -65,22 +65,23 @@ void SendCommandHandler::invoke(openage::event::EventLoop & /* loop */, entity->get_component(component::component_t::COMMANDQUEUE)); switch (command_type) { - case component::command::command_t::IDLE: + case component::command::command_t::IDLE: { command_queue->add_command(time, std::make_shared()); + command_queue->clear_target(time); break; - case component::command::command_t::MOVE: - command_queue->add_command( - time, - std::make_shared( - params.get("target", - coord::phys3{0, 0, 0}))); + } + case component::command::command_t::MOVE: { + auto target_pos = params.get("target", coord::phys3{0, 0, 0}); + command_queue->add_command(time, std::make_shared(target_pos)); + command_queue->set_target(time, target_pos); break; - case component::command::command_t::APPLY_EFFECT: - command_queue->add_command( - time, - std::make_shared( - params.get("target", 0))); + } + case component::command::command_t::APPLY_EFFECT: { + auto target_id = params.get("target", 0); + command_queue->add_command(time, std::make_shared(target_id)); + command_queue->set_target(time, target_id); break; + } default: break; } From 2423d4f76b910a354ef8f90fbd1aac765f3b0eec Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 21:06:06 +0200 Subject: [PATCH 084/118] convert: Add range check before apply effect ability in activity, --- .../conversion/aoc/pregen_processor.py | 70 ++++++++++++++++--- .../convert/service/read/nyan_api_loader.py | 2 +- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index 2b35177a7c..e9d82e071e 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -97,8 +97,9 @@ def generate_activities( # Condition types condition_parent = "engine.util.activity.condition.Condition" - condition_queue_parent = "engine.util.activity.condition.type.CommandInQueue" - condition_next_command_parent = ( + cond_queue_parent = "engine.util.activity.condition.type.CommandInQueue" + cond_target_parent = "engine.util.activity.condition.type.TargetInRange" + cond_command_switch_parent = ( "engine.util.activity.switch_condition.type.NextCommand" ) @@ -244,7 +245,7 @@ def generate_activities( condition_raw_api_object = RawAPIObject(condition_ref_in_modpack, "CommandInQueue", api_objects) condition_raw_api_object.set_location(queue_forward_ref) - condition_raw_api_object.add_raw_parent(condition_queue_parent) + condition_raw_api_object.add_raw_parent(cond_queue_parent) branch_forward_ref = ForwardRef(pregen_converter_group, "util.activity.types.Unit.BranchCommand") @@ -298,23 +299,68 @@ def generate_activities( condition_raw_api_object = RawAPIObject(condition_ref_in_modpack, "NextCommandSwitch", api_objects) condition_raw_api_object.set_location(branch_forward_ref) - condition_raw_api_object.add_raw_parent(condition_next_command_parent) + condition_raw_api_object.add_raw_parent(cond_command_switch_parent) - apply_effect_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.ApplyEffect") + range_check_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.RangeCheck") 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.ApplyEffect"]: range_check_forward_ref, api_objects["engine.util.command.type.Move"]: move_forward_ref, } condition_raw_api_object.add_raw_member("next", next_nodes_lookup, - condition_next_command_parent) + cond_command_switch_parent) pregen_converter_group.add_raw_api_object(condition_raw_api_object) pregen_nyan_objects.update({condition_ref_in_modpack: condition_raw_api_object}) + # Target in range gate + range_check_ref_in_modpack = "util.activity.types.Unit.RangeCheck" + range_check_raw_api_object = RawAPIObject(range_check_ref_in_modpack, + "RangeCheck", api_objects) + range_check_raw_api_object.set_location(unit_forward_ref) + range_check_raw_api_object.add_raw_parent(xor_parent) + + target_in_range_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.TargetInRange") + range_check_raw_api_object.add_raw_member("next", + [target_in_range_forward_ref], + xor_parent) + idle_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Idle") + range_check_raw_api_object.add_raw_member("default", + idle_forward_ref, + xor_parent) + + pregen_converter_group.add_raw_api_object(range_check_raw_api_object) + pregen_nyan_objects.update({range_check_ref_in_modpack: range_check_raw_api_object}) + + # Target in range condition + target_in_range_ref_in_modpack = "util.activity.types.Unit.TargetInRange" + target_in_range_raw_api_object = RawAPIObject(target_in_range_ref_in_modpack, + "TargetInRange", api_objects) + target_in_range_raw_api_object.set_location(unit_forward_ref) + target_in_range_raw_api_object.add_raw_parent(cond_target_parent) + + apply_effect_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.ApplyEffect") + target_in_range_raw_api_object.add_raw_member("next", + apply_effect_forward_ref, + condition_parent) + + target_in_range_raw_api_object.add_raw_member( + "ability", + api_objects["engine.ability.type.ApplyDiscreteEffect"], + cond_target_parent + ) + + pregen_converter_group.add_raw_api_object(target_in_range_raw_api_object) + pregen_nyan_objects.update( + {target_in_range_ref_in_modpack: target_in_range_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, @@ -326,9 +372,11 @@ def generate_activities( "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) + 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}) diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index e55d2996de..1617597e1a 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -3318,7 +3318,7 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: api_object = api_objects["engine.util.activity.condition.type.TargetInRange"] subtype = NyanMemberType(api_objects["engine.ability.Ability"]) - member_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) + member_type = NyanMemberType(MemberType.ABSTRACT, (subtype,)) member = NyanMember("ability", member_type, None, None, 0) api_object.add_member(member) From eea2d79267ead0f1a078efc51155f7e470310716 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 21:28:43 +0200 Subject: [PATCH 085/118] gamestate: Add API interface for checking generic API objects. --- libopenage/gamestate/api/CMakeLists.txt | 1 + libopenage/gamestate/api/object.cpp | 20 +++++++++++++++++ libopenage/gamestate/api/object.h | 30 +++++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 libopenage/gamestate/api/object.cpp create mode 100644 libopenage/gamestate/api/object.h diff --git a/libopenage/gamestate/api/CMakeLists.txt b/libopenage/gamestate/api/CMakeLists.txt index c6735e759a..db07762baf 100644 --- a/libopenage/gamestate/api/CMakeLists.txt +++ b/libopenage/gamestate/api/CMakeLists.txt @@ -4,6 +4,7 @@ add_sources(libopenage animation.cpp definitions.cpp effect.cpp + object.cpp patch.cpp player_setup.cpp property.cpp diff --git a/libopenage/gamestate/api/object.cpp b/libopenage/gamestate/api/object.cpp new file mode 100644 index 0000000000..56d8cbbafc --- /dev/null +++ b/libopenage/gamestate/api/object.cpp @@ -0,0 +1,20 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#include "object.h" + +#include "error/error.h" + + +namespace openage::gamestate::api { + +bool APIObject::is_object(const nyan::Object &obj) { + for (const auto &parent : obj.get_parents()) { + if (parent == "engine.root.Object") { + return true; + } + } + + return false; +} + +} // namespace diff --git a/libopenage/gamestate/api/object.h b/libopenage/gamestate/api/object.h new file mode 100644 index 0000000000..05d830793d --- /dev/null +++ b/libopenage/gamestate/api/object.h @@ -0,0 +1,30 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include + + +namespace nyan { +class Object; +} // namespace nyan + + +namespace openage::gamestate::api { + +/** + * Helper class for getting info on generic objects in the nyan API. + */ +class APIObject { +public: + /** + * Check if a nyan object is an API Object (type == \p engine.root.Object). + * + * @param obj nyan object. + * + * @return true if the object is an object, else false. + */ + static bool is_object(const nyan::Object &obj); +}; + +} // namespace From 75ade5719fd907e370e8232859529b306e3402e2 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 22:29:25 +0200 Subject: [PATCH 086/118] gamestate: More reliable method to find API parent of nyan object. --- libopenage/gamestate/api/ability.cpp | 11 +++-- libopenage/gamestate/api/activity.cpp | 56 +++++++++++++++-------- libopenage/gamestate/api/animation.cpp | 11 +++-- libopenage/gamestate/api/effect.cpp | 11 +++-- libopenage/gamestate/api/patch.cpp | 11 +++-- libopenage/gamestate/api/player_setup.cpp | 11 +++-- libopenage/gamestate/api/property.cpp | 11 +++-- libopenage/gamestate/api/resistance.cpp | 11 +++-- libopenage/gamestate/api/sound.cpp | 11 +++-- libopenage/gamestate/api/terrain.cpp | 11 +++-- libopenage/gamestate/api/util.cpp | 16 ++++++- libopenage/gamestate/api/util.h | 16 ++++++- libopenage/gamestate/entity_factory.cpp | 3 +- 13 files changed, 135 insertions(+), 55 deletions(-) diff --git a/libopenage/gamestate/api/ability.cpp b/libopenage/gamestate/api/ability.cpp index fd1bed4516..a45d8e3afc 100644 --- a/libopenage/gamestate/api/ability.cpp +++ b/libopenage/gamestate/api/ability.cpp @@ -11,6 +11,7 @@ #include "datastructure/constexpr_map.h" #include "gamestate/api/definitions.h" +#include "gamestate/api/util.h" namespace openage::gamestate::api { @@ -27,24 +28,24 @@ bool APIAbility::is_ability(const nyan::Object &obj) { bool APIAbility::check_type(const nyan::Object &ability, const ability_t &type) { - nyan::fqon_t immediate_parent = ability.get_parents()[0]; + nyan::fqon_t api_parent = get_api_parent(ability); 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; + return ability_val->get_name() == api_parent; } ability_t APIAbility::get_type(const nyan::Object &ability) { - nyan::fqon_t immediate_parent = ability.get_parents()[0]; + nyan::fqon_t api_parent = get_api_parent(ability); // TODO: remove once other ability types are implemented - if (not ABILITY_TYPE_LOOKUP.contains(immediate_parent)) { + if (not ABILITY_TYPE_LOOKUP.contains(api_parent)) { return ability_t::UNKNOWN; } - return ABILITY_TYPE_LOOKUP.get(immediate_parent); + return ABILITY_TYPE_LOOKUP.get(api_parent); } component::component_t APIAbility::get_component_type(const nyan::Object &ability) { diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index dd7e337835..55da648b48 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -3,13 +3,19 @@ #include "activity.h" #include "gamestate/api/definitions.h" +#include "gamestate/api/util.h" namespace openage::gamestate::api { bool APIActivity::is_activity(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.util.activity.Activity"; + for (auto &parent : obj.get_parents()) { + if (parent == "engine.util.activity.Activity") { + return true; + } + } + + return false; } nyan::Object APIActivity::get_start(const nyan::Object &activity) { @@ -21,13 +27,19 @@ nyan::Object APIActivity::get_start(const nyan::Object &activity) { bool APIActivityNode::is_node(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.util.activity.node.Node"; + for (auto &parent : obj.get_parents()) { + if (parent == "engine.util.activity.node.Node") { + return true; + } + } + + return false; } activity::node_t APIActivityNode::get_type(const nyan::Object &node) { - nyan::fqon_t immediate_parent = node.get_parents()[0]; - return ACTIVITY_NODE_LOOKUP.get(immediate_parent); + nyan::fqon_t api_parent = get_api_parent(node); + + return ACTIVITY_NODE_LOOKUP.get(api_parent); } std::vector APIActivityNode::get_next(const nyan::Object &node) { @@ -83,7 +95,7 @@ std::vector APIActivityNode::get_next(const nyan::Object &node) { 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_parent = get_api_parent(switch_condition_obj); auto switch_condition_type = ACTIVITY_SWITCH_CONDITION_TYPE_LOOKUP.get(switch_condition_parent); switch (switch_condition_type) { @@ -117,28 +129,33 @@ system::system_id_t APIActivityNode::get_system_id(const nyan::Object &ability_n } bool APIActivityCondition::is_condition(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.util.activity.condition.Condition"; + nyan::fqon_t api_parent = get_api_parent(obj); + + return api_parent == "engine.util.activity.condition.Condition"; } activity::condition_t APIActivityCondition::get_condition(const nyan::Object &condition) { - nyan::fqon_t immediate_parent = condition.get_parents()[0]; - return ACTIVITY_CONDITION_LOOKUP.get(immediate_parent); + nyan::fqon_t api_parent = get_api_parent(condition); + + return ACTIVITY_CONDITION_LOOKUP.get(api_parent); } bool APIActivitySwitchCondition::is_switch_condition(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.util.activity.switch_condition.SwitchCondition"; + nyan::fqon_t api_parent = get_api_parent(obj); + + return api_parent == "engine.util.activity.switch_condition.SwitchCondition"; } activity::switch_function_t APIActivitySwitchCondition::get_switch_func(const nyan::Object &condition) { - nyan::fqon_t immediate_parent = condition.get_parents()[0]; - return ACTIVITY_SWITCH_CONDITION_LOOKUP.get(immediate_parent); + nyan::fqon_t api_parent = get_api_parent(condition); + + return ACTIVITY_SWITCH_CONDITION_LOOKUP.get(api_parent); } APIActivitySwitchCondition::lookup_map_t APIActivitySwitchCondition::get_lookup_map(const nyan::Object &condition) { - nyan::fqon_t immediate_parent = condition.get_parents()[0]; - auto switch_condition_type = ACTIVITY_SWITCH_CONDITION_TYPE_LOOKUP.get(immediate_parent); + nyan::fqon_t api_parent = get_api_parent(condition); + + auto switch_condition_type = ACTIVITY_SWITCH_CONDITION_TYPE_LOOKUP.get(api_parent); switch (switch_condition_type) { case switch_condition_t::NEXT_COMMAND: { @@ -166,8 +183,9 @@ APIActivitySwitchCondition::lookup_map_t APIActivitySwitchCondition::get_lookup_ } bool APIActivityEvent::is_event(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.util.activity.event.Event"; + nyan::fqon_t api_parent = get_api_parent(obj); + + return api_parent == "engine.util.activity.event.Event"; } activity::event_primer_t APIActivityEvent::get_primer(const nyan::Object &event) { diff --git a/libopenage/gamestate/api/animation.cpp b/libopenage/gamestate/api/animation.cpp index 86cbfe6014..a8e62e4f9d 100644 --- a/libopenage/gamestate/api/animation.cpp +++ b/libopenage/gamestate/api/animation.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "animation.h" @@ -10,8 +10,13 @@ namespace openage::gamestate::api { bool APIAnimation::is_animation(nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.ability.property.Property"; + for (auto &parent : obj.get_parents()) { + if (parent == "engine.util.animation.Animation") { + return true; + } + } + + return false; } const std::string APIAnimation::get_animation_path(const nyan::Object &animation) { diff --git a/libopenage/gamestate/api/effect.cpp b/libopenage/gamestate/api/effect.cpp index 691f97b58d..e5b566fe7a 100644 --- a/libopenage/gamestate/api/effect.cpp +++ b/libopenage/gamestate/api/effect.cpp @@ -1,8 +1,9 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "effect.h" #include "gamestate/api/definitions.h" +#include "gamestate/api/util.h" namespace openage::gamestate::api { @@ -19,13 +20,13 @@ bool APIEffect::is_effect(const nyan::Object &obj) { bool APIEffect::check_type(const nyan::Object &effect, const effect_t &type) { - nyan::fqon_t immediate_parent = effect.get_parents()[0]; + nyan::fqon_t api_parent = get_api_parent(effect); 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; + return effect_val->get_name() == api_parent; } bool APIEffect::check_property(const nyan::Object &effect, @@ -37,9 +38,9 @@ bool APIEffect::check_property(const nyan::Object &effect, } effect_t APIEffect::get_type(const nyan::Object &effect) { - nyan::fqon_t immediate_parent = effect.get_parents()[0]; + nyan::fqon_t api_parent = get_api_parent(effect); - return EFFECT_TYPE_LOOKUP.get(immediate_parent); + return EFFECT_TYPE_LOOKUP.get(api_parent); } const nyan::Object APIEffect::get_property(const nyan::Object &effect, diff --git a/libopenage/gamestate/api/patch.cpp b/libopenage/gamestate/api/patch.cpp index 05c2f67d5f..b390c48b9a 100644 --- a/libopenage/gamestate/api/patch.cpp +++ b/libopenage/gamestate/api/patch.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "patch.h" @@ -16,8 +16,13 @@ namespace openage::gamestate::api { bool APIPatch::is_patch(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.util.patch.Patch"; + for (auto &parent : obj.get_parents()) { + if (parent == "engine.util.patch.Patch") { + return true; + } + } + + return false; } bool APIPatch::check_property(const nyan::Object &patch, diff --git a/libopenage/gamestate/api/player_setup.cpp b/libopenage/gamestate/api/player_setup.cpp index 4d01cf327d..d290c75633 100644 --- a/libopenage/gamestate/api/player_setup.cpp +++ b/libopenage/gamestate/api/player_setup.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "player_setup.h" @@ -13,8 +13,13 @@ namespace openage::gamestate::api { bool APIPlayerSetup::is_player_setup(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.util.setup.PlayerSetup"; + for (auto &parent : obj.get_parents()) { + if (parent == "engine.util.setup.PlayerSetup") { + return true; + } + } + + return false; } const std::vector APIPlayerSetup::get_modifiers(const nyan::Object &player_setup) { diff --git a/libopenage/gamestate/api/property.cpp b/libopenage/gamestate/api/property.cpp index fb4458dd5b..c4005ad6a0 100644 --- a/libopenage/gamestate/api/property.cpp +++ b/libopenage/gamestate/api/property.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "property.h" @@ -13,8 +13,13 @@ namespace openage::gamestate::api { bool APIAbilityProperty::is_property(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.ability.property.Property"; + for (auto &parent : obj.get_parents()) { + if (parent == "engine.ability.property.Property") { + return true; + } + } + + return false; } const std::vector APIAbilityProperty::get_animations(const nyan::Object &property) { diff --git a/libopenage/gamestate/api/resistance.cpp b/libopenage/gamestate/api/resistance.cpp index 60a93707e8..b892856dfb 100644 --- a/libopenage/gamestate/api/resistance.cpp +++ b/libopenage/gamestate/api/resistance.cpp @@ -1,8 +1,9 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "resistance.h" #include "gamestate/api/definitions.h" +#include "gamestate/api/util.h" namespace openage::gamestate::api { @@ -19,13 +20,13 @@ bool APIResistance::is_resistance(const nyan::Object &obj) { bool APIResistance::check_effect_type(const nyan::Object &resistance, const effect_t &type) { - nyan::fqon_t immediate_parent = resistance.get_parents()[0]; + nyan::fqon_t api_parent = get_api_parent(resistance); 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; + return effect_val->get_name() == api_parent; } bool APIResistance::check_property(const nyan::Object &resistance, @@ -37,9 +38,9 @@ bool APIResistance::check_property(const nyan::Object &resistance, } effect_t APIResistance::get_effect_type(const nyan::Object &resistance) { - nyan::fqon_t immediate_parent = resistance.get_parents()[0]; + nyan::fqon_t api_parent = get_api_parent(resistance); - return RESISTANCE_TYPE_LOOKUP.get(immediate_parent); + return RESISTANCE_TYPE_LOOKUP.get(api_parent); } const nyan::Object APIResistance::get_property(const nyan::Object &resistance, diff --git a/libopenage/gamestate/api/sound.cpp b/libopenage/gamestate/api/sound.cpp index 0af5fafcf4..9e3b7947c7 100644 --- a/libopenage/gamestate/api/sound.cpp +++ b/libopenage/gamestate/api/sound.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "sound.h" @@ -12,8 +12,13 @@ namespace openage::gamestate::api { bool APISound::is_sound(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.util.sound.Sound"; + for (auto &parent : obj.get_parents()) { + if (parent == "engine.util.sound.Sound") { + return true; + } + } + + return false; } const std::string APISound::get_sound_path(const nyan::Object &sound) { diff --git a/libopenage/gamestate/api/terrain.cpp b/libopenage/gamestate/api/terrain.cpp index ad5e300b47..53e2be027d 100644 --- a/libopenage/gamestate/api/terrain.cpp +++ b/libopenage/gamestate/api/terrain.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "terrain.h" @@ -10,8 +10,13 @@ namespace openage::gamestate::api { bool APITerrain::is_terrain(const nyan::Object &obj) { - nyan::fqon_t immediate_parent = obj.get_parents()[0]; - return immediate_parent == "engine.util.terrain.Terrain"; + for (auto &parent : obj.get_parents()) { + if (parent == "engine.util.terrain.Terrain") { + return true; + } + } + + return false; } const std::string APITerrain::get_terrain_path(const nyan::Object &terrain) { diff --git a/libopenage/gamestate/api/util.cpp b/libopenage/gamestate/api/util.cpp index 7e59ebf9bd..293d3c2efc 100644 --- a/libopenage/gamestate/api/util.cpp +++ b/libopenage/gamestate/api/util.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "util.h" @@ -42,4 +42,18 @@ const std::string resolve_file_path(const nyan::Object &obj, const std::string & } } +const nyan::fqon_t &get_api_parent(const nyan::Object &obj) { + if (obj.get_name().starts_with("engine")) { + return obj.get_name(); + } + + for (const auto &parent : obj.get_parents()) { + if (parent.starts_with("engine.")) { + return parent; + } + } + + throw Error(MSG(err) << "No API parent found for object: " << obj.get_name()); +} + } // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/util.h b/libopenage/gamestate/api/util.h index cad829a205..f3ec3a9446 100644 --- a/libopenage/gamestate/api/util.h +++ b/libopenage/gamestate/api/util.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -24,4 +24,18 @@ namespace openage::gamestate::api { */ const std::string resolve_file_path(const nyan::Object &obj, const std::string &path); +/** + * Get the fqon of the first parent of the object that is defined in the + * API namespace (i.e. it's part of the \p engine namespace). + * + * If the object itself is part of the API namespace, it will return the fqon + * of the object. + * + * @param obj nyan object. + * + * @return fqon of the first parent in the API namespace. + * @throws Error if the object has no parents in the API namespace. + */ +const nyan::fqon_t &get_api_parent(const nyan::Object &obj); + } // namespace openage::gamestate::api diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 8f3da4d24f..bb20453c9f 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -25,6 +25,7 @@ #include "gamestate/activity/xor_switch_gate.h" #include "gamestate/api/ability.h" #include "gamestate/api/activity.h" +#include "gamestate/api/util.h" #include "gamestate/component/api/apply_effect.h" #include "gamestate/component/api/idle.h" #include "gamestate/component/api/line_of_sight.h" @@ -175,7 +176,7 @@ void EntityFactory::init_components(const std::shared_ptr(ability_val.get_ptr())->get_name(); auto ability_obj = owner_db_view->get_object(ability_fqon); - auto ability_parent = ability_obj.get_parents()[0]; + auto ability_parent = api::get_api_parent(ability_obj); auto ability_type = api::APIAbility::get_type(ability_obj); switch (ability_type) { case api::ability_t::MOVE: { From c76f288002d85a8bc7fb37b36be0d5c4d9f8bae8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 22:29:53 +0200 Subject: [PATCH 087/118] gamestate: Add method for checking if game entity/player exists. --- libopenage/gamestate/game_state.cpp | 10 +++++++++- libopenage/gamestate/game_state.h | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/game_state.cpp b/libopenage/gamestate/game_state.cpp index 4bf0aaa348..695423d53a 100644 --- a/libopenage/gamestate/game_state.cpp +++ b/libopenage/gamestate/game_state.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "game_state.h" @@ -63,6 +63,14 @@ const std::shared_ptr &GameState::get_map() const { return this->map; } +bool GameState::has_game_entity(entity_id_t id) const { + return this->game_entities.contains(id); +} + +bool GameState::has_player(player_id_t id) const { + return this->players.contains(id); +} + const std::shared_ptr &GameState::get_mod_manager() const { return this->mod_manager; } diff --git a/libopenage/gamestate/game_state.h b/libopenage/gamestate/game_state.h index 98cdbc187a..02ee31a5d3 100644 --- a/libopenage/gamestate/game_state.h +++ b/libopenage/gamestate/game_state.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -108,6 +108,24 @@ class GameState : public openage::event::State { */ const std::shared_ptr &get_map() const; + /** + * Check whether a game entity with the given ID exists. + * + * @param id ID of the game entity. + * + * @return true if the game entity exists, false otherwise. + */ + bool has_game_entity(entity_id_t id) const; + + /** + * Check whether a player with the given ID exists. + * + * @param id ID of the player. + * + * @return true if the player exists, false otherwise. + */ + bool has_player(player_id_t id) const; + /** * TODO: Only for testing. */ From d7f1e1f58893deba345104b82da6d87834aad691 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 22:30:48 +0200 Subject: [PATCH 088/118] input: Fix type of target ID send in command. --- libopenage/input/controller/game/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/input/controller/game/controller.cpp b/libopenage/input/controller/game/controller.cpp index 4f7bd41e3f..7f3429f02e 100644 --- a/libopenage/input/controller/game/controller.cpp +++ b/libopenage/input/controller/game/controller.cpp @@ -162,7 +162,7 @@ void setup_defaults(const std::shared_ptr &ctx, event::EventHandler::param_map::map_t params{}; - auto target_entity_id = texture_data.read_pixel(args.mouse.x, args.mouse.y); + gamestate::entity_id_t target_entity_id = texture_data.read_pixel(args.mouse.x, args.mouse.y); log::log(DBG << "Targeting entity ID: " << target_entity_id); if (target_entity_id == 0) { auto mouse_pos = args.mouse.to_phys3(camera); From 8ccf07c866be65f3895a3ae2e8fbe882a997f617 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 22:31:15 +0200 Subject: [PATCH 089/118] gamestate: Make TargetInRange condition accessible for activity system. --- .../activity/condition/target_in_range.cpp | 62 ++++++++++++++----- libopenage/gamestate/api/definitions.h | 5 +- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/libopenage/gamestate/activity/condition/target_in_range.cpp b/libopenage/gamestate/activity/condition/target_in_range.cpp index 2308072779..b8f9078688 100644 --- a/libopenage/gamestate/activity/condition/target_in_range.cpp +++ b/libopenage/gamestate/activity/condition/target_in_range.cpp @@ -4,7 +4,10 @@ #include +#include "log/log.h" + #include "gamestate/api/ability.h" +#include "gamestate/component/api_component.h" #include "gamestate/component/internal/command_queue.h" #include "gamestate/component/internal/position.h" #include "gamestate/component/types.h" @@ -18,31 +21,48 @@ bool target_in_range(const time::time_t &time, const std::shared_ptr &entity, const std::shared_ptr &state, const std::shared_ptr &condition) { + log::log(DBG << "Checking TargetInRange cvondition for entity " << entity->get_id()); + auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); auto target = command_queue->get_target().get(time); if (not target.has_value()) { // No target exists, exit early + log::log(DBG << "Target for entity " << entity->get_id() << " is not set"); + return false; + } + + // Get the component type matching the ability in the condition + auto checked_ability_obj = condition->get("TargetInRange.ability"); + auto component_type = api::APIAbility::get_component_type(*checked_ability_obj); + if (not entity->has_component(component_type)) { + // Entity does not have the component matching the ability, exit early + log::log(DBG << "Entity " << entity->get_id() << " does not have a component matching ability '" + << checked_ability_obj->get_name() << "'"); return false; } - auto ability_obj = condition->get("NextCommand.ability"); - auto component_type = api::APIAbility::get_component_type(*ability_obj); - if (not entity->has_component(component_type)) { - // Entity does not have the component matching the ability, exit early - return false; - } + // Get the actual ability used for the range check + // this step is necessary because the ability defined by the condition + // may be abstract, so multiple abilities may be valid + auto component = std::dynamic_pointer_cast( + entity->get_component(component_type)); + auto used_abilty_obj = component->get_ability(); // Fetch min/max range from the ability nyan::value_float_t min_range = 0; nyan::value_float_t max_range = 0; - if (api::APIAbility::check_property(*ability_obj, api::ability_property_t::RANGED)) { + if (api::APIAbility::check_property(used_abilty_obj, api::ability_property_t::RANGED)) { // Get min/max range from the property of the ability - auto range_obj = api::APIAbility::get_property(*ability_obj, api::ability_property_t::RANGED); + log::log(DBG << "Ability " << used_abilty_obj.get_name() << " has Ranged property"); + + auto range_obj = api::APIAbility::get_property(used_abilty_obj, api::ability_property_t::RANGED); min_range = range_obj.get_float("Ranged.min_range"); max_range = range_obj.get_float("Ranged.max_range"); } + log::log(DBG << "Min/Max range for ability " << used_abilty_obj.get_name() << ": " + << min_range << "/" << max_range); // Fetch the current position of the entity auto position = std::dynamic_pointer_cast( @@ -51,22 +71,36 @@ bool target_in_range(const time::time_t &time, if (std::holds_alternative(target.value())) { // Target is a position + log::log(DBG << "Target is a position"); + auto target_pos = std::get(target.value()); + log::log(DBG << "Target position: " << target_pos); + auto distance = (target_pos - current_pos).length(); + log::log(DBG << "Distance to target position: " << distance); return (distance >= min_range and distance <= max_range); } // Target is a game entity auto target_entity_id = std::get(target.value()); - auto target_entity = state->get_game_entity(target_entity_id); + log::log(DBG << "Target is a game entity with ID " << target_entity_id); + if (not state->has_game_entity(target_entity_id)) { + // Target entity does not exist + log::log(DBG << "Target entity " << target_entity_id << " does not exist in state"); + return false; + } + + auto target_entity = state->get_game_entity(target_entity_id); + auto target_position = std::dynamic_pointer_cast( + target_entity->get_component(component::component_t::POSITION)); + auto target_pos = target_position->get_positions().get(time); + log::log(DBG << "Target entity " << target_entity_id << " position: " << target_pos); - auto target_position = std::dynamic_pointer_cast( - target_entity->get_component(component::component_t::POSITION)); - auto target_pos = target_position->get_positions().get(time); - auto distance = (target_pos - current_pos).length(); + auto distance = (target_pos - current_pos).length(); + log::log(DBG << "Distance to target entity " << target_entity_id << ": " << distance); - return (distance >= min_range and distance <= max_range); + return (distance >= min_range and distance <= max_range); } } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index 5081539eab..0735684c70 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -11,6 +11,7 @@ #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/condition/target_in_range.h" #include "gamestate/activity/event/command_in_queue.h" #include "gamestate/activity/event/wait.h" #include "gamestate/activity/types.h" @@ -305,7 +306,9 @@ static const auto ACTIVITY_CONDITION_LOOKUP = datastructure::create_const_map Date: Sun, 11 May 2025 23:42:19 +0200 Subject: [PATCH 090/118] gamestate: Remove unnecessary parameters from ApplyEffect system. --- libopenage/gamestate/system/apply_effect.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index 52e7a8557a..c0e52eda9b 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -21,7 +21,7 @@ namespace openage::gamestate::system { const time::time_t ApplyEffect::apply_effect(const std::shared_ptr &effector, - const std::shared_ptr &state, + const std::shared_ptr & /* state */, const std::shared_ptr &resistor, const time::time_t &start_time) { auto effects_component = std::dynamic_pointer_cast( @@ -120,8 +120,7 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptrget_ability(); - handle_animated(effector, ability, start_time); + handle_animated(effector, effect_ability, start_time); return total_time; } From ffff0b1198ed9824b26096ddd5b95b4355503edb Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 12 May 2025 00:01:57 +0200 Subject: [PATCH 091/118] convert: Add task nodes to nyan API loader. --- .../convert/service/read/nyan_api_loader.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index 1617597e1a..7985a38e50 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -609,6 +609,13 @@ 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.Task + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("Task", parents) + fqon = "engine.util.activity.node.type.Task" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.util.activity.node.type.XOREventGate parents = [api_objects["engine.util.activity.node.Node"]] nyan_object = NyanObject("XOREventGate", parents) @@ -644,6 +651,27 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.util.activity.task.Task + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Task", parents) + fqon = "engine.util.activity.task.Task" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.task.type.ClearCommandQueue + parents = [api_objects["engine.util.activity.task.Task"]] + nyan_object = NyanObject("ClearCommandQueue", parents) + fqon = "engine.util.activity.task.type.ClearCommandQueue" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.task.type.PopCommandQueue + parents = [api_objects["engine.util.activity.task.Task"]] + nyan_object = NyanObject("PopCommandQueue", parents) + fqon = "engine.util.activity.task.type.PopCommandQueue" + 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) @@ -3346,6 +3374,17 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("next", member_type, None, None, 0) api_object.add_member(member) + # engine.util.activity.node.type.Task + api_object = api_objects["engine.util.activity.node.type.Task"] + + member_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member = NyanMember("next", member_type, None, None, 0) + api_object.add_member(member) + subtype = NyanMemberType(api_objects["engine.util.activity.task.Task"]) + member_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) + member = NyanMember("task", member_type, None, None, 0) + api_object.add_member(member) + # engine.util.activity.node.type.XOREventGate api_object = api_objects["engine.util.activity.node.type.XOREventGate"] From 98ecb0ec8554bc0ce552da5b944684147ed3dad0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 12 May 2025 00:08:49 +0200 Subject: [PATCH 092/118] convert: Add clear queue task to default activity. --- .../conversion/aoc/pregen_processor.py | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index e9d82e071e..b6fe94fe11 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -91,6 +91,7 @@ def generate_activities( start_parent = "engine.util.activity.node.type.Start" end_parent = "engine.util.activity.node.type.End" ability_parent = "engine.util.activity.node.type.Ability" + task_parent = "engine.util.activity.node.type.Task" 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" @@ -328,10 +329,10 @@ def generate_activities( range_check_raw_api_object.add_raw_member("next", [target_in_range_forward_ref], xor_parent) - idle_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.Idle") + queue_clear_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.ClearCommandQueue") range_check_raw_api_object.add_raw_member("default", - idle_forward_ref, + queue_clear_forward_ref, xor_parent) pregen_converter_group.add_raw_api_object(range_check_raw_api_object) @@ -361,6 +362,27 @@ def generate_activities( {target_in_range_ref_in_modpack: target_in_range_raw_api_object} ) + # Clear command queue + clear_command_ref_in_modpack = "util.activity.types.Unit.ClearCommandQueue" + clear_command_raw_api_object = RawAPIObject(clear_command_ref_in_modpack, + "ClearCommandQueue", api_objects) + clear_command_raw_api_object.set_location(unit_forward_ref) + clear_command_raw_api_object.add_raw_parent(task_parent) + + clear_command_raw_api_object.add_raw_member("next", + idle_forward_ref, + task_parent) + clear_command_raw_api_object.add_raw_member( + "task", + api_objects["engine.util.activity.task.type.ClearCommandQueue"], + task_parent + ) + + pregen_converter_group.add_raw_api_object(clear_command_raw_api_object) + pregen_nyan_objects.update( + {clear_command_ref_in_modpack: clear_command_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, From 88947821ecce693b663f4924c8778df6d2abb6f3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 12 May 2025 00:56:13 +0200 Subject: [PATCH 093/118] curve: Fix breakout condition when clearing queue. --- libopenage/curve/container/queue.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libopenage/curve/container/queue.h b/libopenage/curve/container/queue.h index 6d33a54627..5626456061 100644 --- a/libopenage/curve/container/queue.h +++ b/libopenage/curve/container/queue.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -427,8 +427,8 @@ void Queue::clear(const time::time_t &time) { } // erase all elements alive at t <= time - while (this->container.at(at).alive() <= time - and at != this->container.size()) { + while (at != this->container.size() + and this->container.at(at).alive() <= time) { if (this->container.at(at).dead() > time) { this->container[at].set_dead(time); } From b69e45262fad49ad45c82e3ee0329b5a1a644893 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 12 May 2025 00:59:03 +0200 Subject: [PATCH 094/118] gamestate: Use clear queue task in activity system. --- libopenage/gamestate/api/activity.cpp | 41 ++++++++++++++++--- libopenage/gamestate/api/definitions.h | 8 +++- libopenage/gamestate/entity_factory.cpp | 15 ++++++- libopenage/gamestate/system/CMakeLists.txt | 1 + libopenage/gamestate/system/activity.cpp | 7 ++++ libopenage/gamestate/system/command_queue.cpp | 39 ++++++++++++++++++ libopenage/gamestate/system/command_queue.h | 41 +++++++++++++++++++ libopenage/gamestate/system/types.h | 7 +++- 8 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 libopenage/gamestate/system/command_queue.cpp create mode 100644 libopenage/gamestate/system/command_queue.h diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index 55da648b48..535cfea1ca 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -50,7 +50,20 @@ std::vector APIActivityNode::get_next(const nyan::Object &node) { } // 1 next node case activity::node_t::TASK_SYSTEM: { - auto next = node.get("Ability.next"); + auto api_parent = get_api_parent(node); + + nyan::memberid_t member_name; + if (api_parent == "engine.util.activity.node.type.Ability") { + member_name = "Ability.next"; + } + else if (api_parent == "engine.util.activity.node.type.Task") { + member_name = "Task.next"; + } + else { + throw Error(MSG(err) << "Node type '" << api_parent << "' cannot be used to get the next node."); + } + + auto next = node.get(member_name); std::shared_ptr db_view = node.get_view(); return {db_view->get_object(next->get_name())}; } @@ -118,14 +131,30 @@ std::vector APIActivityNode::get_next(const nyan::Object &node) { } } -system::system_id_t APIActivityNode::get_system_id(const nyan::Object &ability_node) { - auto ability = ability_node.get("Ability.ability"); +system::system_id_t APIActivityNode::get_system_id(const nyan::Object &node) { + nyan::fqon_t api_parent = get_api_parent(node); + + nyan::fqon_t task_obj_fqon; + if (api_parent == "engine.util.activity.node.type.Ability") { + task_obj_fqon = node.get("Ability.ability")->get_name(); + } + else if (api_parent == "engine.util.activity.node.type.Task") { + task_obj_fqon = node.get("Task.task")->get_name(); + } + else { + throw Error(MSG(err) << "Node type '" << api_parent << "' cannot be used to get the system ID."); + } + + // Get the API parent of the task object to look up the system ID + auto view = node.get_view(); + auto task_obj = view->get_object(task_obj_fqon); + task_obj_fqon = get_api_parent(task_obj); - if (not ACTIVITY_TASK_SYSTEM_LOOKUP.contains(ability->get_name())) [[unlikely]] { - throw Error(MSG(err) << "Ability '" << ability->get_name() << "' has no associated system defined."); + if (not ACTIVITY_TASK_SYSTEM_LOOKUP.contains(task_obj_fqon)) [[unlikely]] { + throw Error(MSG(err) << "'" << task_obj.get_name() << "' has no associated system defined."); } - return ACTIVITY_TASK_SYSTEM_LOOKUP.get(ability->get_name()); + return ACTIVITY_TASK_SYSTEM_LOOKUP.get(task_obj_fqon); } bool APIActivityCondition::is_condition(const nyan::Object &obj) { diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index 0735684c70..d87e65b592 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -279,6 +279,8 @@ static const auto ACTIVITY_NODE_LOOKUP = datastructure::create_const_map(activity_node); - auto output_fqon = nyan_node.get("Ability.next")->get_name(); + + auto api_parent = api::get_api_parent(nyan_node); + nyan::memberid_t member_name; + if (api_parent == "engine.util.activity.node.type.Ability") { + member_name = "Ability.next"; + } + else if (api_parent == "engine.util.activity.node.type.Task") { + member_name = "Task.next"; + } + else { + throw Error{ERR << "Node type '" << api_parent << "' cannot be used to get the next node."}; + } + + auto output_fqon = nyan_node.get(member_name)->get_name(); auto output_id = visited[output_fqon]; auto output_node = node_id_map[output_id]; task_system->add_output(output_node); diff --git a/libopenage/gamestate/system/CMakeLists.txt b/libopenage/gamestate/system/CMakeLists.txt index 025729409a..57a3ef3591 100644 --- a/libopenage/gamestate/system/CMakeLists.txt +++ b/libopenage/gamestate/system/CMakeLists.txt @@ -1,6 +1,7 @@ add_sources(libopenage activity.cpp apply_effect.cpp + command_queue.cpp idle.cpp move.cpp property.cpp diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index 072da8bab1..ad9ae8a6c7 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.cpp @@ -21,6 +21,7 @@ #include "gamestate/component/types.h" #include "gamestate/game_entity.h" #include "gamestate/system/apply_effect.h" +#include "gamestate/system/command_queue.h" #include "gamestate/system/idle.h" #include "gamestate/system/move.h" #include "util/fixed_point.h" @@ -153,6 +154,12 @@ const time::time_t Activity::handle_subsystem(const time::time_t &start_time, // TODO: replace destination value with a parameter return Move::move_default(entity, state, {1, 1, 1}, start_time); break; + case system_id_t::CLEAR_COMMAND_QUEUE: + return CommandQueue::clear_queue(entity, start_time); + break; + case system_id_t::POP_COMMAND_QUEUE: + return CommandQueue::pop_command(entity, start_time); + break; default: throw Error{ERR << "Unhandled subsystem " << static_cast(system_id)}; } diff --git a/libopenage/gamestate/system/command_queue.cpp b/libopenage/gamestate/system/command_queue.cpp new file mode 100644 index 0000000000..161a25095b --- /dev/null +++ b/libopenage/gamestate/system/command_queue.cpp @@ -0,0 +1,39 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#include "command_queue.h" + +#include "gamestate/game_entity.h" +#include "gamestate/component/internal/command_queue.h" +#include "gamestate/component/types.h" + + +namespace openage::gamestate::system { + +const time::time_t CommandQueue::clear_queue(const std::shared_ptr &entity, + const time::time_t &start_time) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + + // Clear the queue + auto &queue = command_queue->get_queue(); + queue.clear(start_time); + + // Clear the target + command_queue->clear_target(start_time); + + return start_time; +} + +const time::time_t CommandQueue::pop_command(const std::shared_ptr &entity, + const time::time_t &start_time) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + + // Pop the command + auto &queue = command_queue->get_queue(); + queue.pop_front(start_time); + + return start_time; +} + +} // namespace diff --git a/libopenage/gamestate/system/command_queue.h b/libopenage/gamestate/system/command_queue.h new file mode 100644 index 0000000000..e8f9ea09b9 --- /dev/null +++ b/libopenage/gamestate/system/command_queue.h @@ -0,0 +1,41 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "time/time.h" + + +namespace openage::gamestate { +class GameEntity; + +namespace system { + +class CommandQueue { +public: + /** + * Clear the command queue of the entity. + * + * @param entity Game entity. + * @param start_time Start time of change. + * + * @return Runtime of the change in simulation time. + */ + static const time::time_t clear_queue(const std::shared_ptr &entity, + const time::time_t &start_time); + + /** + * Pop the front command from the command queue of the entity. + * + * @param entity Game entity. + * @param start_time Start time of change. + * + * @return Runtime of the change in simulation time. + */ + static const time::time_t pop_command(const std::shared_ptr &entity, + const time::time_t &start_time); +}; + +} // namespace system +} // namespace openage::gamestate diff --git a/libopenage/gamestate/system/types.h b/libopenage/gamestate/system/types.h index 9930b47b9b..7dad1ca0e7 100644 --- a/libopenage/gamestate/system/types.h +++ b/libopenage/gamestate/system/types.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -11,6 +11,7 @@ namespace openage::gamestate::system { enum class system_id_t { NONE, + // ability systems APPLY_EFFECT, IDLE, @@ -19,6 +20,10 @@ enum class system_id_t { MOVE_DEFAULT, ACTIVITY_ADVANCE, + + // tasks + CLEAR_COMMAND_QUEUE, + POP_COMMAND_QUEUE, }; } // namespace openage::gamestate::system From 9ed03d5541e7133edf407bb465bbad5d60c7128c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 01:46:22 +0200 Subject: [PATCH 095/118] gamestate: Make apply effect system use command information. --- libopenage/gamestate/system/activity.cpp | 2 +- libopenage/gamestate/system/apply_effect.cpp | 21 ++++++++++++++++++++ libopenage/gamestate/system/apply_effect.h | 15 +++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index ad9ae8a6c7..dd6b88e6f7 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.cpp @@ -142,7 +142,7 @@ const time::time_t Activity::handle_subsystem(const time::time_t &start_time, system_id_t system_id) { switch (system_id) { case system_id_t::APPLY_EFFECT: - return ApplyEffect::apply_effect(entity, state, entity, start_time); + return ApplyEffect::apply_effect_command(entity, state, start_time); break; case system_id_t::IDLE: return Idle::idle(entity, start_time); diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index c0e52eda9b..4c95f6a08b 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -12,6 +12,8 @@ #include "gamestate/component/api/apply_effect.h" #include "gamestate/component/api/live.h" #include "gamestate/component/api/resistance.h" +#include "gamestate/component/internal/command_queue.h" +#include "gamestate/component/internal/commands/apply_effect.h" #include "gamestate/component/types.h" #include "gamestate/game_entity.h" #include "gamestate/game_state.h" @@ -19,6 +21,25 @@ namespace openage::gamestate::system { +const time::time_t ApplyEffect::apply_effect_command(const std::shared_ptr &entity, + const std::shared_ptr &state, + const time::time_t &start_time) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + auto command = std::dynamic_pointer_cast( + command_queue->pop_command(start_time)); + + if (not command) [[unlikely]] { + log::log(MSG(warn) << "Command is not a move command."); + return time::time_t::from_int(0); + } + + auto resistor_id = command->get_target(); + auto resistor = state->get_game_entity(resistor_id); + + return ApplyEffect::apply_effect(entity, state, resistor, start_time); +} + const time::time_t ApplyEffect::apply_effect(const std::shared_ptr &effector, const std::shared_ptr & /* state */, diff --git a/libopenage/gamestate/system/apply_effect.h b/libopenage/gamestate/system/apply_effect.h index d7b9f02450..2cc6746e64 100644 --- a/libopenage/gamestate/system/apply_effect.h +++ b/libopenage/gamestate/system/apply_effect.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -21,6 +21,19 @@ namespace system { class ApplyEffect { public: + /** + * Apply the effect of an ability from a command. + * + * @param entity Game entity applying the effects. + * @param state Game state. + * @param start_time Start time of change. + * + * @return Runtime of the change in simulation time. + */ + static const time::time_t apply_effect_command(const std::shared_ptr &entity, + const std::shared_ptr &state, + const time::time_t &start_time); + /** * Apply the effect of an ability to a game entity. * From 89d026776649e81dcd5cb541a8068d7fd9c71a43 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 02:41:36 +0200 Subject: [PATCH 096/118] gamestate: Remove separate queue for command queue targets. --- libopenage/curve/container/queue.h | 4 ++ .../activity/condition/command_in_queue.cpp | 2 +- .../activity/condition/next_command.cpp | 4 +- .../condition/next_command_switch.cpp | 4 +- .../activity/condition/target_in_range.cpp | 2 +- .../activity/event/command_in_queue.cpp | 4 +- .../component/internal/command_queue.cpp | 47 +++++++++---- .../component/internal/command_queue.h | 67 +++++++------------ libopenage/gamestate/event/send_command.cpp | 3 - libopenage/gamestate/system/apply_effect.cpp | 2 +- libopenage/gamestate/system/command_queue.cpp | 15 ++--- libopenage/gamestate/system/move.cpp | 4 +- 12 files changed, 78 insertions(+), 80 deletions(-) diff --git a/libopenage/curve/container/queue.h b/libopenage/curve/container/queue.h index 5626456061..510fe32bf0 100644 --- a/libopenage/curve/container/queue.h +++ b/libopenage/curve/container/queue.h @@ -70,6 +70,8 @@ class Queue : public event::EventEntity { * * Ignores dead elements. * + * Note: Calling this function on an empty queue is undefined behavior. + * * @param time The time to get the element at. * * @return Queue element. @@ -96,6 +98,8 @@ class Queue : public event::EventEntity { * * Ignores dead elements. * + * Note: Calling this function on an empty queue is undefined behavior. + * * @param time The time to get the element at. * @param value Queue element. */ diff --git a/libopenage/gamestate/activity/condition/command_in_queue.cpp b/libopenage/gamestate/activity/condition/command_in_queue.cpp index 299ca1870a..8f8db57c7d 100644 --- a/libopenage/gamestate/activity/condition/command_in_queue.cpp +++ b/libopenage/gamestate/activity/condition/command_in_queue.cpp @@ -15,7 +15,7 @@ bool command_in_queue(const time::time_t &time, auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); - return not command_queue->get_queue().empty(time); + return not command_queue->get_commands().empty(time); } } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/condition/next_command.cpp b/libopenage/gamestate/activity/condition/next_command.cpp index ff7d965603..57ea9e20fa 100644 --- a/libopenage/gamestate/activity/condition/next_command.cpp +++ b/libopenage/gamestate/activity/condition/next_command.cpp @@ -18,11 +18,11 @@ bool next_command(const time::time_t &time, auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); - if (command_queue->get_queue().empty(time)) { + if (command_queue->get_commands().empty(time)) { return false; } - auto queue_command = command_queue->get_queue().front(time); + auto queue_command = command_queue->get_commands().front(time); auto compare_command = condition->get("NextCommand.command"); auto compare_type = api::COMMAND_LOOKUP.get(compare_command->get_name()); diff --git a/libopenage/gamestate/activity/condition/next_command_switch.cpp b/libopenage/gamestate/activity/condition/next_command_switch.cpp index 7133f6381a..33626c4338 100644 --- a/libopenage/gamestate/activity/condition/next_command_switch.cpp +++ b/libopenage/gamestate/activity/condition/next_command_switch.cpp @@ -15,11 +15,11 @@ int next_command_switch(const time::time_t &time, auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); - if (command_queue->get_queue().empty(time)) { + if (command_queue->get_commands().empty(time)) { return -1; } - auto command = command_queue->get_queue().front(time); + auto command = command_queue->get_commands().front(time); return static_cast(command->get_type()); } diff --git a/libopenage/gamestate/activity/condition/target_in_range.cpp b/libopenage/gamestate/activity/condition/target_in_range.cpp index b8f9078688..ce602bebae 100644 --- a/libopenage/gamestate/activity/condition/target_in_range.cpp +++ b/libopenage/gamestate/activity/condition/target_in_range.cpp @@ -25,7 +25,7 @@ bool target_in_range(const time::time_t &time, auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); - auto target = command_queue->get_target().get(time); + auto target = command_queue->get_target(time); if (not target.has_value()) { // No target exists, exit early diff --git a/libopenage/gamestate/activity/event/command_in_queue.cpp b/libopenage/gamestate/activity/event/command_in_queue.cpp index af57692078..9d4b5211a1 100644 --- a/libopenage/gamestate/activity/event/command_in_queue.cpp +++ b/libopenage/gamestate/activity/event/command_in_queue.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "command_in_queue.h" @@ -26,7 +26,7 @@ std::shared_ptr primer_command_in_queue(const time::time_ params); auto entity_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); - auto &queue = entity_queue->get_queue(); + auto &queue = const_cast> &>(entity_queue->get_commands()); queue.add_dependent(ev); return ev; diff --git a/libopenage/gamestate/component/internal/command_queue.cpp b/libopenage/gamestate/component/internal/command_queue.cpp index 5832b938c4..a3f41d05b0 100644 --- a/libopenage/gamestate/component/internal/command_queue.cpp +++ b/libopenage/gamestate/component/internal/command_queue.cpp @@ -4,14 +4,15 @@ #include +#include "gamestate/component/internal/commands/apply_effect.h" +#include "gamestate/component/internal/commands/move.h" #include "gamestate/component/types.h" namespace openage::gamestate::component { CommandQueue::CommandQueue(const std::shared_ptr &loop) : - command_queue{loop, 0}, - target{loop, 0} { + command_queue{loop, 0} { } inline component_t CommandQueue::get_type() const { @@ -23,28 +24,46 @@ void CommandQueue::add_command(const time::time_t &time, this->command_queue.insert(time, command); } -curve::Queue> &CommandQueue::get_queue() { +const curve::Queue> &CommandQueue::get_commands() { return this->command_queue; } -const std::shared_ptr CommandQueue::pop_command(const time::time_t &time) { - return this->command_queue.pop_front(time); +void CommandQueue::clear(const time::time_t &time) { + this->command_queue.clear(time); } -curve::Discrete &CommandQueue::get_target() { - return this->target; -} +const std::shared_ptr CommandQueue::pop(const time::time_t &time) { + if (this->command_queue.empty(time)) { + return nullptr; + } -void CommandQueue::set_target(const time::time_t &time, const coord::phys3 &target) { - this->target.set_last(time, target); + return this->command_queue.pop_front(time); } -void CommandQueue::set_target(const time::time_t &time, const entity_id_t target) { - this->target.set_last(time, target); +const std::shared_ptr CommandQueue::front(const time::time_t &time) const { + if (this->command_queue.empty(time)) { + return nullptr; + } + + return this->command_queue.front(time); } -void CommandQueue::clear_target(const time::time_t &time) { - this->target.set_last(time, std::nullopt); +CommandQueue::optional_target_t CommandQueue::get_target(const time::time_t &time) const { + if (this->command_queue.empty(time)) { + return std::nullopt; + } + + auto command = this->command_queue.front(time); + + // Extract the target from the command + switch (command->get_type()) { + case command::command_t::MOVE: + return std::dynamic_pointer_cast(command)->get_target(); + case command::command_t::APPLY_EFFECT: + return std::dynamic_pointer_cast(command)->get_target(); + default: + return std::nullopt; + } } } // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index 13b9f1ef4f..fd6e58faa7 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -48,76 +48,61 @@ class CommandQueue final : public InternalComponent { * * @return Command queue. */ - curve::Queue> &get_queue(); + const curve::Queue> &get_commands(); /** - * Get the command in the front of the queue. + * Clear all commands in the queue. * - * @param time Time at which the command is retrieved. - * - * @return Command in the front of the queue or nullptr if the queue is empty. + * @param time Time at which the queue is cleared. */ - const std::shared_ptr pop_command(const time::time_t &time); + void clear(const time::time_t &time); /** - * Target type with several possible representations. + * Get the command in the front of the queue and remove it. * - * Can be: - * - coord::phys3: Position in the game world. - * - entity_id_t: ID of another entity. - * - std::nullopt: Nothing. - */ - using optional_target_t = std::optional>; - - /** - * Get the targets of the entity over time. + * Unlike curve::Queue::front(), calling this on an empty queue is + * not undefined behavior. + * + * @param time Time at which the command is popped. * - * @return Targets over time. + * @return Command in the front of the queue or nullptr if the queue is empty. */ - curve::Discrete &get_target(); + const std::shared_ptr pop(const time::time_t &time); /** - * Set the target of the entity to a position in the game world. + * get the command at the front of the queue. * - * All target after \p time are deleted. + * @param time Time at which the command is retrieved. * - * @param time Time at which the target is set. - * @param target Target position in the game world. + * @return Command in the front of the queue or nullptr if the queue is empty. */ - void set_target(const time::time_t &time, const coord::phys3 &target); + const std::shared_ptr front(const time::time_t &time) const; /** - * Set the target of the entity to another entity. - * - * All targets after \p time are deleted. + * Target type with several possible representations. * - * @param time Time at which the target is set. - * @param target Target entity ID. + * Can be: + * - coord::phys3: Position in the game world. + * - entity_id_t: ID of another entity. + * - std::nullopt: Nothing. */ - void set_target(const time::time_t &time, const entity_id_t target); + using optional_target_t = std::optional>; /** - * Set the target of the entity to nothing. + * Get the target of the entity at the given time. * - * All targets after \p time are deleted. + * The target may be empty if the command queue is empty or if the command + * has no target. * - * @param time Time at which the target is cleared. + * @return Target of the entity. */ - void clear_target(const time::time_t &time); + optional_target_t get_target(const time::time_t &time) const; private: /** * Command queue. */ curve::Queue> command_queue; - - /** - * Target of the entity. - * - * TODO: We could also figure out the target via the commands. Then we - * don't need to store the target separately. - */ - curve::Discrete target; }; } // namespace gamestate::component diff --git a/libopenage/gamestate/event/send_command.cpp b/libopenage/gamestate/event/send_command.cpp index 9fb864d651..3af4eac800 100644 --- a/libopenage/gamestate/event/send_command.cpp +++ b/libopenage/gamestate/event/send_command.cpp @@ -67,19 +67,16 @@ 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->clear_target(time); break; } case component::command::command_t::MOVE: { auto target_pos = params.get("target", coord::phys3{0, 0, 0}); command_queue->add_command(time, std::make_shared(target_pos)); - command_queue->set_target(time, target_pos); break; } case component::command::command_t::APPLY_EFFECT: { auto target_id = params.get("target", 0); command_queue->add_command(time, std::make_shared(target_id)); - command_queue->set_target(time, target_id); break; } default: diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index 4c95f6a08b..ecf07183b8 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -27,7 +27,7 @@ const time::time_t ApplyEffect::apply_effect_command(const std::shared_ptr( entity->get_component(component::component_t::COMMANDQUEUE)); auto command = std::dynamic_pointer_cast( - command_queue->pop_command(start_time)); + command_queue->pop(start_time)); if (not command) [[unlikely]] { log::log(MSG(warn) << "Command is not a move command."); diff --git a/libopenage/gamestate/system/command_queue.cpp b/libopenage/gamestate/system/command_queue.cpp index 161a25095b..4879f0b603 100644 --- a/libopenage/gamestate/system/command_queue.cpp +++ b/libopenage/gamestate/system/command_queue.cpp @@ -14,14 +14,9 @@ const time::time_t CommandQueue::clear_queue(const std::shared_ptr( entity->get_component(component::component_t::COMMANDQUEUE)); - // Clear the queue - auto &queue = command_queue->get_queue(); - queue.clear(start_time); + command_queue->clear(start_time); - // Clear the target - command_queue->clear_target(start_time); - - return start_time; + return start_time; } const time::time_t CommandQueue::pop_command(const std::shared_ptr &entity, @@ -29,11 +24,9 @@ const time::time_t CommandQueue::pop_command(const std::shared_ptr( entity->get_component(component::component_t::COMMANDQUEUE)); - // Pop the command - auto &queue = command_queue->get_queue(); - queue.pop_front(start_time); + command_queue->pop(start_time); - return start_time; + return start_time; } } // namespace diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index eccd97694a..aeb2d25e93 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "move.h" @@ -82,7 +82,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( - command_queue->pop_command(start_time)); + command_queue->pop(start_time)); if (not command) [[unlikely]] { log::log(MSG(warn) << "Command is not a move command."); From d8b97899e2f74d1e65799a3ce55a243c2948b2e2 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 02:55:17 +0200 Subject: [PATCH 097/118] gamestate: Add system for moving to target. --- libopenage/gamestate/system/activity.cpp | 3 +++ libopenage/gamestate/system/move.cpp | 32 ++++++++++++++++++++++++ libopenage/gamestate/system/move.h | 17 ++++++++++++- libopenage/gamestate/system/types.h | 1 + 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index dd6b88e6f7..b4c3daa1e9 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.cpp @@ -150,6 +150,9 @@ const time::time_t Activity::handle_subsystem(const time::time_t &start_time, case system_id_t::MOVE_COMMAND: return Move::move_command(entity, state, start_time); break; + case system_id_t::MOVE_TARGET: + return Move::move_target(entity, state, start_time); + break; case system_id_t::MOVE_DEFAULT: // TODO: replace destination value with a parameter return Move::move_default(entity, state, {1, 1, 1}, start_time); diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index aeb2d25e93..7229305ae2 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -92,6 +92,38 @@ const time::time_t Move::move_command(const std::shared_ptrget_target(), start_time); } +const time::time_t Move::move_target(const std::shared_ptr &entity, + const std::shared_ptr &state, + const time::time_t &start_time) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + auto target = command_queue->get_target(start_time); + + if (not target.has_value()) [[unlikely]] { + log::log(WARN << "Entity " << entity->get_id() << " has no target at time " << start_time); + return time::time_t::from_int(0); + } + + if (std::holds_alternative(target.value())) { + auto target_id = std::get(target.value()); + auto target_entity = state->get_game_entity(target_id); + + auto position = std::dynamic_pointer_cast( + target_entity->get_component(component::component_t::POSITION)); + + auto target_pos = position->get_positions().get(start_time); + + return Move::move_default(entity, state, target_pos, start_time); + } + else if (std::holds_alternative(target.value())) { + auto target_pos = std::get(target.value()); + return Move::move_default(entity, state, target_pos, start_time); + } + + log::log(WARN << "Entity " << entity->get_id() << " has an invalid target at time " << start_time); + return time::time_t::from_int(0); +} + const time::time_t Move::move_default(const std::shared_ptr &entity, const std::shared_ptr &state, diff --git a/libopenage/gamestate/system/move.h b/libopenage/gamestate/system/move.h index 346c1c0aa6..81fa1d1c64 100644 --- a/libopenage/gamestate/system/move.h +++ b/libopenage/gamestate/system/move.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -19,6 +19,8 @@ class Move { /** * Move a game entity to a destination from a move command. * + * Consumes (pops) the move command from the command queue. + * * @param entity Game entity. * @param state Game state. * @param start_time Start time of change. @@ -29,6 +31,19 @@ class Move { const std::shared_ptr &state, const time::time_t &start_time); + /** + * Move a game entity to the current target of the game entity. + * + * @param entity Game entity. + * @param state Game state. + * @param start_time Start time of change. + * + * @return Runtime of the change in simulation time. + */ + static const time::time_t move_target(const std::shared_ptr &entity, + const std::shared_ptr &state, + const time::time_t &start_time); + /** * Move a game entity to a destination. * diff --git a/libopenage/gamestate/system/types.h b/libopenage/gamestate/system/types.h index 7dad1ca0e7..f2da58700c 100644 --- a/libopenage/gamestate/system/types.h +++ b/libopenage/gamestate/system/types.h @@ -17,6 +17,7 @@ enum class system_id_t { IDLE, MOVE_COMMAND, + MOVE_TARGET, MOVE_DEFAULT, ACTIVITY_ADVANCE, From 023b281494f352390db3fe514833d41c9b8c87dc Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 02:59:10 +0200 Subject: [PATCH 098/118] gamestate: Private default movement to deestination. --- libopenage/gamestate/activity/tests/node_types.cpp | 2 +- libopenage/gamestate/system/activity.cpp | 4 ---- libopenage/gamestate/system/move.h | 1 + libopenage/gamestate/system/types.h | 1 - 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/libopenage/gamestate/activity/tests/node_types.cpp b/libopenage/gamestate/activity/tests/node_types.cpp index d8237a6d9a..bdd90ec2b4 100644 --- a/libopenage/gamestate/activity/tests/node_types.cpp +++ b/libopenage/gamestate/activity/tests/node_types.cpp @@ -75,7 +75,7 @@ void node_types() { // 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; + auto sytem_id = system::system_id_t::IDLE; task_system_node->set_system_id(sytem_id); // Check the system ID diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index b4c3daa1e9..33b7982b3b 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.cpp @@ -153,10 +153,6 @@ const time::time_t Activity::handle_subsystem(const time::time_t &start_time, case system_id_t::MOVE_TARGET: return Move::move_target(entity, state, start_time); break; - case system_id_t::MOVE_DEFAULT: - // TODO: replace destination value with a parameter - return Move::move_default(entity, state, {1, 1, 1}, start_time); - break; case system_id_t::CLEAR_COMMAND_QUEUE: return CommandQueue::clear_queue(entity, start_time); break; diff --git a/libopenage/gamestate/system/move.h b/libopenage/gamestate/system/move.h index 81fa1d1c64..65d2f9656d 100644 --- a/libopenage/gamestate/system/move.h +++ b/libopenage/gamestate/system/move.h @@ -44,6 +44,7 @@ class Move { const std::shared_ptr &state, const time::time_t &start_time); +private: /** * Move a game entity to a destination. * diff --git a/libopenage/gamestate/system/types.h b/libopenage/gamestate/system/types.h index f2da58700c..5be3385e8a 100644 --- a/libopenage/gamestate/system/types.h +++ b/libopenage/gamestate/system/types.h @@ -18,7 +18,6 @@ enum class system_id_t { MOVE_COMMAND, MOVE_TARGET, - MOVE_DEFAULT, ACTIVITY_ADVANCE, From ee4de8d15f8af4cf40746c44781f44bc6deb158b Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 03:44:12 +0200 Subject: [PATCH 099/118] convert: Move to target in default unit activity. --- .../conversion/aoc/pregen_processor.py | 67 +++++++++++++------ .../convert/service/read/nyan_api_loader.py | 7 ++ 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index b6fe94fe11..75dd243a9e 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -329,10 +329,10 @@ def generate_activities( range_check_raw_api_object.add_raw_member("next", [target_in_range_forward_ref], xor_parent) - queue_clear_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.ClearCommandQueue") + move_to_target_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.MoveToTarget") range_check_raw_api_object.add_raw_member("default", - queue_clear_forward_ref, + move_to_target_forward_ref, xor_parent) pregen_converter_group.add_raw_api_object(range_check_raw_api_object) @@ -362,27 +362,52 @@ def generate_activities( {target_in_range_ref_in_modpack: target_in_range_raw_api_object} ) - # Clear command queue - clear_command_ref_in_modpack = "util.activity.types.Unit.ClearCommandQueue" - clear_command_raw_api_object = RawAPIObject(clear_command_ref_in_modpack, - "ClearCommandQueue", api_objects) - clear_command_raw_api_object.set_location(unit_forward_ref) - clear_command_raw_api_object.add_raw_parent(task_parent) - - clear_command_raw_api_object.add_raw_member("next", - idle_forward_ref, - task_parent) - clear_command_raw_api_object.add_raw_member( + # Move to target task + move_to_target_ref_in_modpack = "util.activity.types.Unit.MoveToTarget" + move_to_target_raw_api_object = RawAPIObject(move_to_target_ref_in_modpack, + "MoveToTarget", api_objects) + move_to_target_raw_api_object.set_location(unit_forward_ref) + move_to_target_raw_api_object.add_raw_parent(task_parent) + + wait_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.WaitMoveToTarget") + move_to_target_raw_api_object.add_raw_member("next", + wait_forward_ref, + task_parent) + move_to_target_raw_api_object.add_raw_member( "task", - api_objects["engine.util.activity.task.type.ClearCommandQueue"], + api_objects["engine.util.activity.task.type.MoveToTarget"], task_parent ) - pregen_converter_group.add_raw_api_object(clear_command_raw_api_object) + pregen_converter_group.add_raw_api_object(move_to_target_raw_api_object) pregen_nyan_objects.update( - {clear_command_ref_in_modpack: clear_command_raw_api_object} + {move_to_target_ref_in_modpack: move_to_target_raw_api_object} ) + # Wait for MoveToTarget task (for movement to finish) + wait_ref_in_modpack = "util.activity.types.Unit.WaitMoveToTarget" + wait_raw_api_object = RawAPIObject(wait_ref_in_modpack, + "WaitMoveToTarget", api_objects) + wait_raw_api_object.set_location(unit_forward_ref) + wait_raw_api_object.add_raw_parent(xor_event_parent) + + wait_finish = api_objects["engine.util.activity.event.type.WaitAbility"] + wait_command = api_objects["engine.util.activity.event.type.CommandInQueue"] + range_check_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.RangeCheck") + branch_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.BranchCommand") + wait_raw_api_object.add_raw_member("next", + { + wait_finish: range_check_forward_ref, + wait_command: branch_forward_ref + }, + xor_event_parent) + + pregen_converter_group.add_raw_api_object(wait_raw_api_object) + pregen_nyan_objects.update({wait_ref_in_modpack: wait_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, @@ -391,7 +416,7 @@ def generate_activities( apply_effect_raw_api_object.add_raw_parent(ability_parent) wait_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.Wait") + "util.activity.types.Unit.WaitAbility") apply_effect_raw_api_object.add_raw_member("next", wait_forward_ref, ability_parent) apply_effect_raw_api_object.add_raw_member( @@ -411,7 +436,7 @@ def generate_activities( move_raw_api_object.add_raw_parent(ability_parent) wait_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.Wait") + "util.activity.types.Unit.WaitAbility") move_raw_api_object.add_raw_member("next", wait_forward_ref, ability_parent) move_raw_api_object.add_raw_member("ability", @@ -421,8 +446,8 @@ def generate_activities( pregen_converter_group.add_raw_api_object(move_raw_api_object) pregen_nyan_objects.update({move_ref_in_modpack: move_raw_api_object}) - # Wait (for Move or Command) - wait_ref_in_modpack = "util.activity.types.Unit.Wait" + # Wait after ability usage (for Move/ApplyEffect or new command) + wait_ref_in_modpack = "util.activity.types.Unit.WaitAbility" wait_raw_api_object = RawAPIObject(wait_ref_in_modpack, "Wait", api_objects) wait_raw_api_object.set_location(unit_forward_ref) diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index 7985a38e50..48e7c1cbc2 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -665,6 +665,13 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.util.activity.task.type.MoveToTarget + parents = [api_objects["engine.util.activity.task.Task"]] + nyan_object = NyanObject("MoveToTarget", parents) + fqon = "engine.util.activity.task.type.MoveToTarget" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.util.activity.task.type.PopCommandQueue parents = [api_objects["engine.util.activity.task.Task"]] nyan_object = NyanObject("PopCommandQueue", parents) From 774a6e5490c989cbee376885c5b807222dc2e61e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 03:45:59 +0200 Subject: [PATCH 100/118] gamestate: Handle MoveToTarget task activity type. --- libopenage/gamestate/api/definitions.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index d87e65b592..e941d22c13 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -303,7 +303,9 @@ static const auto ACTIVITY_TASK_SYSTEM_LOOKUP = datastructure::create_const_map< std::pair("engine.util.activity.task.type.ClearCommandQueue", system::system_id_t::CLEAR_COMMAND_QUEUE), std::pair("engine.util.activity.task.type.PopCommandQueue", - system::system_id_t::POP_COMMAND_QUEUE)); + system::system_id_t::POP_COMMAND_QUEUE), + std::pair("engine.util.activity.task.type.MoveToTarget", + system::system_id_t::MOVE_TARGET)); /** * Maps API activity condition types to engine condition types. From 4c72b8aa3730629c242a123178339b9ea3cbdea1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 03:54:39 +0200 Subject: [PATCH 101/118] gamestate: Fix wrong log messages. --- libopenage/gamestate/activity/condition/target_in_range.cpp | 2 +- libopenage/gamestate/system/apply_effect.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/activity/condition/target_in_range.cpp b/libopenage/gamestate/activity/condition/target_in_range.cpp index ce602bebae..ca95650084 100644 --- a/libopenage/gamestate/activity/condition/target_in_range.cpp +++ b/libopenage/gamestate/activity/condition/target_in_range.cpp @@ -21,7 +21,7 @@ bool target_in_range(const time::time_t &time, const std::shared_ptr &entity, const std::shared_ptr &state, const std::shared_ptr &condition) { - log::log(DBG << "Checking TargetInRange cvondition for entity " << entity->get_id()); + log::log(DBG << "Checking TargetInRange condition for entity " << entity->get_id()); auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index ecf07183b8..bf95685a7b 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -30,7 +30,7 @@ const time::time_t ApplyEffect::apply_effect_command(const std::shared_ptrpop(start_time)); if (not command) [[unlikely]] { - log::log(MSG(warn) << "Command is not a move command."); + log::log(MSG(warn) << "Command is not a apply effect command."); return time::time_t::from_int(0); } From 5949f88316ae54c3c6b193b27bb613233e77153e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 04:25:37 +0200 Subject: [PATCH 102/118] gamestate: Clear command queue by default when new command is sent from input system. --- .../gamestate/component/internal/command_queue.cpp | 6 ++++++ .../gamestate/component/internal/command_queue.h | 13 +++++++++++-- libopenage/gamestate/event/send_command.cpp | 6 +++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/libopenage/gamestate/component/internal/command_queue.cpp b/libopenage/gamestate/component/internal/command_queue.cpp index a3f41d05b0..d05e2ea216 100644 --- a/libopenage/gamestate/component/internal/command_queue.cpp +++ b/libopenage/gamestate/component/internal/command_queue.cpp @@ -24,6 +24,12 @@ void CommandQueue::add_command(const time::time_t &time, this->command_queue.insert(time, command); } +void CommandQueue::set_command(const time::time_t &time, + const std::shared_ptr &command) { + this->command_queue.clear(time); + this->command_queue.insert(time, command); +} + const curve::Queue> &CommandQueue::get_commands() { return this->command_queue; } diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index fd6e58faa7..440a6a0eea 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -35,14 +35,23 @@ class CommandQueue final : public InternalComponent { component_t get_type() const override; /** - * Adds a command to the queue. + * Append a command to the queue. * - * @param time Time at which the command is added. + * @param time Time at which the command is appended. * @param command New command. */ void add_command(const time::time_t &time, const std::shared_ptr &command); + /** + * Clear the queue and set the front command. + * + * @param time Time at which the command is set. + * @param command New command. + */ + void set_command(const time::time_t &time, + const std::shared_ptr &command); + /** * Get the command queue. * diff --git a/libopenage/gamestate/event/send_command.cpp b/libopenage/gamestate/event/send_command.cpp index 3af4eac800..1f5cbc2d16 100644 --- a/libopenage/gamestate/event/send_command.cpp +++ b/libopenage/gamestate/event/send_command.cpp @@ -66,17 +66,17 @@ 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->set_command(time, std::make_shared()); break; } case component::command::command_t::MOVE: { auto target_pos = params.get("target", coord::phys3{0, 0, 0}); - command_queue->add_command(time, std::make_shared(target_pos)); + command_queue->set_command(time, std::make_shared(target_pos)); break; } case component::command::command_t::APPLY_EFFECT: { auto target_id = params.get("target", 0); - command_queue->add_command(time, std::make_shared(target_id)); + command_queue->set_command(time, std::make_shared(target_id)); break; } default: From e95c440e05b5e39a49eca05f60885f7bb9c7a174 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 14:09:27 +0200 Subject: [PATCH 103/118] convert: Add activity node for checking if the game entity has an ability. --- .../conversion/aoc/pregen_processor.py | 69 ++++++++++++++++++- .../convert/service/read/nyan_api_loader.py | 15 ++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index 75dd243a9e..963fa22a79 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -98,6 +98,7 @@ def generate_activities( # Condition types condition_parent = "engine.util.activity.condition.Condition" + cond_ability_parent = "engine.util.activity.condition.type.AbilityUsable" cond_queue_parent = "engine.util.activity.condition.type.CommandInQueue" cond_target_parent = "engine.util.activity.condition.type.TargetInRange" cond_command_switch_parent = ( @@ -302,12 +303,12 @@ def generate_activities( condition_raw_api_object.set_location(branch_forward_ref) condition_raw_api_object.add_raw_parent(cond_command_switch_parent) - range_check_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.RangeCheck") + ability_check_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.ApplyEffectUsableCheck") move_forward_ref = ForwardRef(pregen_converter_group, "util.activity.types.Unit.Move") next_nodes_lookup = { - api_objects["engine.util.command.type.ApplyEffect"]: range_check_forward_ref, + api_objects["engine.util.command.type.ApplyEffect"]: ability_check_forward_ref, api_objects["engine.util.command.type.Move"]: move_forward_ref, } condition_raw_api_object.add_raw_member("next", @@ -317,6 +318,68 @@ def generate_activities( pregen_converter_group.add_raw_api_object(condition_raw_api_object) pregen_nyan_objects.update({condition_ref_in_modpack: condition_raw_api_object}) + # Ability usability gate + ability_check_ref_in_modpack = "util.activity.types.Unit.ApplyEffectUsableCheck" + ability_check_raw_api_object = RawAPIObject(ability_check_ref_in_modpack, + "ApplyEffectUsableCheck", api_objects) + ability_check_raw_api_object.set_location(unit_forward_ref) + ability_check_raw_api_object.add_raw_parent(xor_parent) + + condition_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.ApplyEffectUsable") + ability_check_raw_api_object.add_raw_member("next", + [condition_forward_ref], + xor_parent) + pop_command_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.PopCommand") + ability_check_raw_api_object.add_raw_member("default", + pop_command_forward_ref, + xor_parent) + + pregen_converter_group.add_raw_api_object(ability_check_raw_api_object) + pregen_nyan_objects.update({ability_check_ref_in_modpack: ability_check_raw_api_object}) + + # Apply effect usability condition + apply_effect_ref_in_modpack = "util.activity.types.Unit.ApplyEffectUsable" + apply_effect_raw_api_object = RawAPIObject(apply_effect_ref_in_modpack, + "ApplyEffectUsable", api_objects) + apply_effect_raw_api_object.set_location(unit_forward_ref) + apply_effect_raw_api_object.add_raw_parent(cond_ability_parent) + + target_in_range_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.RangeCheck") + apply_effect_raw_api_object.add_raw_member("next", + target_in_range_forward_ref, + condition_parent) + apply_effect_raw_api_object.add_raw_member( + "ability", + api_objects["engine.ability.type.ApplyDiscreteEffect"], + cond_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}) + + # Pop command task + pop_command_ref_in_modpack = "util.activity.types.Unit.PopCommand" + pop_command_raw_api_object = RawAPIObject(pop_command_ref_in_modpack, + "PopCommand", api_objects) + pop_command_raw_api_object.set_location(unit_forward_ref) + pop_command_raw_api_object.add_raw_parent(task_parent) + + idle_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Idle") + pop_command_raw_api_object.add_raw_member("next", idle_forward_ref, + task_parent) + pop_command_raw_api_object.add_raw_member( + "task", + api_objects["engine.util.activity.task.type.PopCommandQueue"], + task_parent + ) + + pregen_converter_group.add_raw_api_object(pop_command_raw_api_object) + pregen_nyan_objects.update({pop_command_ref_in_modpack: pop_command_raw_api_object}) + # Target in range gate range_check_ref_in_modpack = "util.activity.types.Unit.RangeCheck" range_check_raw_api_object = RawAPIObject(range_check_ref_in_modpack, diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index 48e7c1cbc2..de5cf9a61b 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -532,6 +532,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.AbilityUsable + parents = [api_objects["engine.util.activity.condition.Condition"]] + nyan_object = NyanObject("AbilityUsable", parents) + fqon = "engine.util.activity.condition.type.AbilityUsable" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.util.activity.condition.type.CommandInQueue parents = [api_objects["engine.util.activity.condition.Condition"]] nyan_object = NyanObject("CommandInQueue", parents) @@ -3341,6 +3348,14 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("next", member_type, None, None, 0) api_object.add_member(member) + # engine.util.activity.condition.type.AbilityUsable + api_object = api_objects["engine.util.activity.condition.type.AbilityUsable"] + + subtype = NyanMemberType(api_objects["engine.ability.Ability"]) + member_type = NyanMemberType(MemberType.ABSTRACT, (subtype,)) + member = NyanMember("ability", member_type, None, None, 0) + api_object.add_member(member) + # engine.util.activity.condition.type.NextCommand api_object = api_objects["engine.util.activity.condition.type.NextCommand"] From d65a9d8b7629afc9648effd1820e9910a2f7adf1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 14:29:20 +0200 Subject: [PATCH 104/118] gamestate: Add condition function for AbilityUsable check. --- .../activity/condition/CMakeLists.txt | 1 + .../activity/condition/ability_usable.cpp | 24 ++++++++++++ .../activity/condition/ability_usable.h | 37 +++++++++++++++++++ .../activity/condition/target_in_range.h | 4 +- libopenage/gamestate/api/definitions.h | 5 ++- 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 libopenage/gamestate/activity/condition/ability_usable.cpp create mode 100644 libopenage/gamestate/activity/condition/ability_usable.h diff --git a/libopenage/gamestate/activity/condition/CMakeLists.txt b/libopenage/gamestate/activity/condition/CMakeLists.txt index fc61f4a363..1e1ef2a811 100644 --- a/libopenage/gamestate/activity/condition/CMakeLists.txt +++ b/libopenage/gamestate/activity/condition/CMakeLists.txt @@ -1,4 +1,5 @@ add_sources(libopenage + ability_usable.cpp command_in_queue.cpp next_command_switch.cpp next_command.cpp diff --git a/libopenage/gamestate/activity/condition/ability_usable.cpp b/libopenage/gamestate/activity/condition/ability_usable.cpp new file mode 100644 index 0000000000..6fc44a4ac5 --- /dev/null +++ b/libopenage/gamestate/activity/condition/ability_usable.cpp @@ -0,0 +1,24 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#include "ability_usable.h" + +#include + +#include "gamestate/game_entity.h" +#include "gamestate/api/ability.h" + + +namespace openage::gamestate::activity { + +bool component_enabled(const time::time_t &/* time */, + const std::shared_ptr &entity, + const std::shared_ptr & /* state */, + const std::shared_ptr &condition) { + auto ability_obj = condition->get("AbilityUsable.ability"); + auto component_type = api::APIAbility::get_component_type(*ability_obj); + + // TODO: Check if the component is enabled at time + return entity->has_component(component_type); +} + +} // namespace diff --git a/libopenage/gamestate/activity/condition/ability_usable.h b/libopenage/gamestate/activity/condition/ability_usable.h new file mode 100644 index 0000000000..fb3161cf0d --- /dev/null +++ b/libopenage/gamestate/activity/condition/ability_usable.h @@ -0,0 +1,37 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace nyan { +class Object; +} + +namespace openage::gamestate { +class GameEntity; +class GameState; + +namespace activity { + +/** + * Check whether the entity has a component enabled matching the ability from the + * given API object. + * + * @param time Time when the condition is checked. + * @param entity Game entity that the activity is assigned to. + * @param condition nyan object for the condition. Used to read the ability reference. + * + * @return true if there is at least one component enabled matching the ability, false otherwise. + */ +bool component_enabled(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr & /* state */, + const std::shared_ptr &condition); + +} // namespace activity +} // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/condition/target_in_range.h b/libopenage/gamestate/activity/condition/target_in_range.h index 3b3562c4bf..a40c195637 100644 --- a/libopenage/gamestate/activity/condition/target_in_range.h +++ b/libopenage/gamestate/activity/condition/target_in_range.h @@ -31,14 +31,14 @@ namespace activity { * * @param time Time when the condition is checked. * @param entity Game entity that the activity is assigned to. - * @param api_object nyan object for the condition. Used to read the command type. + * @param condition nyan object for the condition. Used to read the ability reference. * * @return true if the target is within range of the ability, false otherwise. */ bool target_in_range(const time::time_t &time, const std::shared_ptr &entity, const std::shared_ptr &state, - const std::shared_ptr &api_object); + const std::shared_ptr &condition); } // namespace activity } // namespace openage::gamestate diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index e941d22c13..0566ab8e7a 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -8,6 +8,7 @@ #include #include "datastructure/constexpr_map.h" +#include "gamestate/activity/condition/ability_usable.h" #include "gamestate/activity/condition/command_in_queue.h" #include "gamestate/activity/condition/next_command.h" #include "gamestate/activity/condition/next_command_switch.h" @@ -316,7 +317,9 @@ static const auto ACTIVITY_CONDITION_LOOKUP = datastructure::create_const_map Date: Sun, 18 May 2025 14:49:03 +0200 Subject: [PATCH 105/118] gamestate: Turn entity towards target before applying effect. --- libopenage/gamestate/system/apply_effect.cpp | 44 +++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index bf95685a7b..1cebbbc7e8 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -12,8 +12,10 @@ #include "gamestate/component/api/apply_effect.h" #include "gamestate/component/api/live.h" #include "gamestate/component/api/resistance.h" +#include "gamestate/component/api/turn.h" #include "gamestate/component/internal/command_queue.h" #include "gamestate/component/internal/commands/apply_effect.h" +#include "gamestate/component/internal/position.h" #include "gamestate/component/types.h" #include "gamestate/game_entity.h" #include "gamestate/game_state.h" @@ -45,6 +47,45 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptr & /* state */, const std::shared_ptr &resistor, const time::time_t &start_time) { + time::time_t total_time = 0; + + // rotate towards resistor + auto turn_component_effector = std::dynamic_pointer_cast( + effector->get_component(component::component_t::TURN)); + auto turn_ability = turn_component_effector->get_ability(); + auto turn_speed = turn_ability.get("Turn.turn_speed"); + + auto pos_component_effector = std::dynamic_pointer_cast( + effector->get_component(component::component_t::POSITION)); + auto pos_component_resistor = std::dynamic_pointer_cast( + resistor->get_component(component::component_t::POSITION)); + auto effector_pos = pos_component_effector->get_positions().get(start_time); + auto resistor_pos = pos_component_resistor->get_positions().get(start_time); + auto effector_angle = pos_component_effector->get_angles().get(start_time); + + auto path_vector = resistor_pos - effector_pos; + auto path_angle = path_vector.to_angle(); + + if (not turn_speed->is_infinite_positive()) { + auto angle_diff = path_angle - effector_angle; + if (angle_diff < 0) { + // get the positive difference + angle_diff = angle_diff * -1; + } + if (angle_diff > 180) { + // always use the smaller angle + angle_diff = angle_diff - 360; + angle_diff = angle_diff * -1; + } + + double turn_time = angle_diff.to_double() / turn_speed->get(); + total_time += turn_time; + + // TODO: Delay application of effect until the turn is finished + } + pos_component_effector->set_angle(start_time + total_time, path_angle); + + // start applications auto effects_component = std::dynamic_pointer_cast( effector->get_component(component::component_t::APPLY_EFFECT)); auto effect_ability = effects_component->get_ability(); @@ -92,8 +133,7 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptr Date: Sun, 18 May 2025 15:24:38 +0200 Subject: [PATCH 106/118] doc: Move system descriptions into code docs. --- doc/code/game_simulation/game_entity.md | 13 +++++-- .../game_simulation/images/system_idle.svg | 28 --------------- .../game_simulation/images/system_move.svg | 30 ---------------- doc/code/game_simulation/systems.md | 36 ------------------- libopenage/gamestate/system/activity.h | 5 ++- libopenage/gamestate/system/apply_effect.h | 21 ++++++++--- libopenage/gamestate/system/idle.h | 4 ++- libopenage/gamestate/system/move.h | 16 +++++++++ 8 files changed, 51 insertions(+), 102 deletions(-) delete mode 100644 doc/code/game_simulation/images/system_idle.svg delete mode 100644 doc/code/game_simulation/images/system_move.svg delete mode 100644 doc/code/game_simulation/systems.md diff --git a/doc/code/game_simulation/game_entity.md b/doc/code/game_simulation/game_entity.md index b511a626a9..71f55a8711 100644 --- a/doc/code/game_simulation/game_entity.md +++ b/doc/code/game_simulation/game_entity.md @@ -7,6 +7,7 @@ Game entities represent objects inside the game world. 3. [Component Data Storage](#component-data-storage) 4. [Control Flow](#control-flow) 1. [System](#system) + 1. [System Types](#system-types) 2. [Activities](#activities) 3. [Manager](#manager) @@ -88,8 +89,6 @@ make the game logic maintanable and extensible. ### System -For a description of the available systems, check the [system reference](systems.md). - A *system* in openage is basically a function that operates on game entity components. They are explicitely separated from game entity and component objects to allow for more flexible implementation. In practice, systems are implemented as static @@ -108,6 +107,16 @@ Exceptions should only be made for direct subsystems implementing subroutines or to avoid code redundancies. The reasoning behind this is that dependencies between systems may quickly become unmanageable. +#### System Types + +| Type | Description | +| -------------- | ------------------------------------------ | +| `Activity` | Handle control flow in the activity graph | +| `ApplyEffect` | Use the `ApplyEffect` ability of an entity | +| `CommandQueue` | Control the command queue on an entity | +| `Idle` | Use the `Idle` ability of an entity | +| `Move` | Use the `Move` ability of an entity | + ### Activities diff --git a/doc/code/game_simulation/images/system_idle.svg b/doc/code/game_simulation/images/system_idle.svg deleted file mode 100644 index 3da9289b05..0000000000 --- a/doc/code/game_simulation/images/system_idle.svg +++ /dev/null @@ -1,28 +0,0 @@ - - -Idleidle(GameEntity, time_t): time_t diff --git a/doc/code/game_simulation/images/system_move.svg b/doc/code/game_simulation/images/system_move.svg deleted file mode 100644 index 46966a4b4f..0000000000 --- a/doc/code/game_simulation/images/system_move.svg +++ /dev/null @@ -1,30 +0,0 @@ - - -Movemove_default(GameEntity, phys3, time_t): time_tmove_command(GameEntity, time_t): time_t diff --git a/doc/code/game_simulation/systems.md b/doc/code/game_simulation/systems.md deleted file mode 100644 index 61ea2fcf34..0000000000 --- a/doc/code/game_simulation/systems.md +++ /dev/null @@ -1,36 +0,0 @@ -# Built-in Systems - -Overview of the built-in systems in the game simulation. - -1. [Idle](#idle) -2. [Move](#move) - - -## Idle - -![Idle systems class UML](images/system_idle.svg) - -Handles idle actions for game entities. - -`idle(..)` updates the animation of the game entity. This requires the game -entity to have the `Idle` component. The function returns a time of 0 since -no actionsconsuming simulation time are taken. - - -## Move - -![Move systems class UML](images/system_move.svg) - -Handles movement actions for game entities. - -`move_default(..)` moves a game entity to the new position specified in the function -call. This requires the game entity to have the `Move` and `Turn` components. -Waypoints for the exact path are fetched from the pathfinder. -For every straight path between waypoints, the game entity is turned first, then -moved (same as in *Age of Empires*). If an animation is available for the `Move` -component, this animation is forwarded as the game entity's active animation to the -renderer. The function returns the cumulative time of all turn and movement actions -initiated by this function. - -`move_command(..)` processes the payload from a move *command* to call `move_default(..)` -with the payload parameters. diff --git a/libopenage/gamestate/system/activity.h b/libopenage/gamestate/system/activity.h index 11dcce68ca..4dd0237613 100644 --- a/libopenage/gamestate/system/activity.h +++ b/libopenage/gamestate/system/activity.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -27,6 +27,9 @@ class Activity { /** * Advance in the activity flow graph of the game entity. * + * Visits and executes actions for the current node until a node that + * requires an event to be triggered is reached. + * * @param start_time Start time of change. * @param entity Game entity. */ diff --git a/libopenage/gamestate/system/apply_effect.h b/libopenage/gamestate/system/apply_effect.h index 2cc6746e64..64c6aff15b 100644 --- a/libopenage/gamestate/system/apply_effect.h +++ b/libopenage/gamestate/system/apply_effect.h @@ -22,7 +22,12 @@ namespace system { class ApplyEffect { public: /** - * Apply the effect of an ability from a command. + * Apply the effect of an ability from a command fetched from the command queue. + * + * The front command in the command queue is expected to be of type `ApplyEffect`. If + * not, the command is ignored and a runtime of 0 is returned. + * + * Consumes (pops) the front command from the command queue. * * @param entity Game entity applying the effects. * @param state Game state. @@ -34,8 +39,17 @@ class ApplyEffect { const std::shared_ptr &state, const time::time_t &start_time); +private: /** - * Apply the effect of an ability to a game entity. + * Apply the effect of an ability of one game entity (\p effector) to another + * game entity (\p resistor). + * + * The effector requires an enabled `ApplyEffect` and `Turn` component. + * The entity requires an enabled `Resistance` component. + * + * The effector takes the following actions: + * - Rotate towards the resistor. + * - Apply the effects of the ability to the resistor. * * @param effector Game entity applying the effects. * @param state Game state. @@ -49,13 +63,12 @@ class ApplyEffect { 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) + * applied_value = clamp(change_value - block_value, min_change, max_change) * * Effects and resistances MUST have the same attribute change type. * diff --git a/libopenage/gamestate/system/idle.h b/libopenage/gamestate/system/idle.h index eb4434fdb7..4f3446e496 100644 --- a/libopenage/gamestate/system/idle.h +++ b/libopenage/gamestate/system/idle.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -17,6 +17,8 @@ class Idle { /** * Let a game entity idle. * + * The entity requires an enabled `Idle` component. + * * This does not change the state of a unit. It only changes its animation and * sounds. * diff --git a/libopenage/gamestate/system/move.h b/libopenage/gamestate/system/move.h index 65d2f9656d..c3b5bf445e 100644 --- a/libopenage/gamestate/system/move.h +++ b/libopenage/gamestate/system/move.h @@ -19,6 +19,9 @@ class Move { /** * Move a game entity to a destination from a move command. * + * The front command in the command queue is expected to be of type `Move`. If + * not, the command is ignored and a runtime of 0 is returned. + * * Consumes (pops) the move command from the command queue. * * @param entity Game entity. @@ -34,6 +37,9 @@ class Move { /** * Move a game entity to the current target of the game entity. * + * The target is fetched from the command queue. If no target is set, the + * command is ignored and a runtime of 0 is returned. + * * @param entity Game entity. * @param state Game state. * @param start_time Start time of change. @@ -48,6 +54,16 @@ class Move { /** * Move a game entity to a destination. * + * The entity requires an enabled `Move` and `Turn` component. + * + * The entity takes the following actions: + * - use the pathfinding system to find a waypoint path to the destination + * - for each waypoint: + * - rotate towards the waypoint + * - move to the waypoint + * + * These action mirror the movement behavior of units in Age of Empires II. + * * @param entity Game entity. * @param state Game state. * @param destination Destination coordinates. From fef0bc589ef8306bc04625b85d1a6297ade9a156 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 17:00:07 +0200 Subject: [PATCH 107/118] doc: Move component descriptions into code docs. --- doc/code/curves.md | 2 +- doc/code/game_simulation/activity.md | 72 ++++++++-- doc/code/game_simulation/components.md | 130 ------------------ doc/code/game_simulation/game_entity.md | 15 -- .../images/component_activity_uml.svg | 82 ----------- .../images/component_command_queue_uml.svg | 51 ------- .../images/component_idle_uml.svg | 25 ---- .../images/component_live_uml.svg | 33 ----- .../images/component_move_uml.svg | 25 ---- .../images/component_ownership_uml.svg | 33 ----- .../images/component_position_uml.svg | 39 ------ .../images/component_turn_uml.svg | 25 ---- .../gamestate/component/api/apply_effect.h | 41 ++++-- libopenage/gamestate/component/api/idle.h | 6 +- .../gamestate/component/api/line_of_sight.h | 5 +- libopenage/gamestate/component/api/live.h | 6 + libopenage/gamestate/component/api/move.h | 5 +- .../gamestate/component/api/resistance.h | 8 +- .../gamestate/component/api/selectable.h | 5 +- libopenage/gamestate/component/api/turn.h | 5 +- .../gamestate/component/internal/activity.h | 37 +++-- .../component/internal/command_queue.h | 5 + .../gamestate/component/internal/ownership.h | 7 +- .../gamestate/component/internal/position.h | 7 +- 24 files changed, 165 insertions(+), 504 deletions(-) delete mode 100644 doc/code/game_simulation/components.md delete mode 100644 doc/code/game_simulation/images/component_activity_uml.svg delete mode 100644 doc/code/game_simulation/images/component_command_queue_uml.svg delete mode 100644 doc/code/game_simulation/images/component_idle_uml.svg delete mode 100644 doc/code/game_simulation/images/component_live_uml.svg delete mode 100644 doc/code/game_simulation/images/component_move_uml.svg delete mode 100644 doc/code/game_simulation/images/component_ownership_uml.svg delete mode 100644 doc/code/game_simulation/images/component_position_uml.svg delete mode 100644 doc/code/game_simulation/images/component_turn_uml.svg diff --git a/doc/code/curves.md b/doc/code/curves.md index 3d3cdd2613..6473df2b6e 100644 --- a/doc/code/curves.md +++ b/doc/code/curves.md @@ -316,7 +316,7 @@ keyframe. `sync(Curve, t)` also supports compression with a flag `compress` pass 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 transferred 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 diff --git a/doc/code/game_simulation/activity.md b/doc/code/game_simulation/activity.md index edd623f419..2116084e98 100644 --- a/doc/code/game_simulation/activity.md +++ b/doc/code/game_simulation/activity.md @@ -5,7 +5,10 @@ configurable. 1. [Motivation](#motivation) 2. [Architecture](#architecture) -3. [Node Types](#node-types) +3. [Workflow](#workflow) + 1. [Initialization](#initialization) + 2. [Advancing in the graph](#advancing-in-the-graph) +4. [Node Types](#node-types) ## Motivation @@ -32,7 +35,20 @@ and event triggers that indicate which path to take next. By traversing the node its paths, the game entities actions are determined. The currently visited node in the graph corresponds to the current action of a unit. -Activities are reusable, i.e. they are intended to be shared by many game entities Usually, +Advancement to the next node can be initiated in several ways, depending on the +[node type](#node-types) of the current node. +It can happen automatically or be triggered by an event. In the latter case, +the event is handled by the `GameEntityManager` which calls an activity *system* +that processes the event to choose the next node. + +Advancing in the graph, i.e. visiting nodes and performing actions costs no ingame time. Time +delays of actions, e.g. for using an game mechanic like movement, are instead handled by +scheduling and waiting for events at certain nodes in the graph (e.g. `XOR_EVENT_GATE` nodes). +This means that when running the activity system, the directed edges of the nodes are followed +until a node that waits for an event is reached. This allows the activity graph to support +complex action chains that can be executed in sequence. + +Activities are reusable, i.e. they are intended to be shared by many game entities. Usually, all game entities of the same type should share the same behaviour, so they get assigned the same activity node graph. @@ -45,15 +61,47 @@ representation. You don't need to know BPMN to understand the activity control f we explain everything important about the graphs in our documentation. However, you can use available [BPMN tools](https://bpmn.io/) to draw activity node graphs. -## Node Types +Like all game data, activities and node types for game entities are defined via the +[nyan API](doc/nyan/openage-lib.md). + + +## Workflow + +![Activity Workflow](images/activity_workflow.png) + +### Initialization +When a game entity is spawned, the engine first checks whether entity's `GameEntity` API object +has an ability `Activity` assigned. If that is the case, the activity graph is loaded from +the corresponding API objects defining the graph. Most of this step involves creates the +nodes and connections for the graph as well as mapping the API objects to node actions. + +The loaded activity graph is stored in a `Activity` component that is assigned to the game +entity. At this point, the activity state of the entity is still uninitialized which allows +the entity or the component to be cached for faster assignment to entities using the same graph. +To let the entity become active, the `init(..)` method of the Activity component should be +called after the entity is completely initialized. This sets the activity state to the start +node of the actvity graph. + +### Advancing in the graph + +After the game entity is spawned, the `GameEntityManager` is called once to trigger the initial +behavior of the game entity. This advances the activity state until the first event branch where +an event is required for further advancement. The `GameEntityManager` now waits for events +for the entity to further advance in the graph. + +A game entity's current activity state is stored in its `Activity` component in form of +a reference to the current node. Additionally, the components stores the list of events +the entity currently waits for to advance. + +## 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 | -| `XOR_SWITCH_GATE` | 1 | 1+ | Branch on value | +| Type | Description | Inputs | Outputs | +| ----------------- | ------------------------- | ------ | ------- | +| `START` | Start of activity | 0 | 1 | +| `END` | End of activity | 1 | 0 | +| `TASK_SYSTEM` | Run built-in system | 1 | 1 | +| `TASK_CUSTOM` | Run custom function | 1 | 1 | +| `XOR_EVENT_GATE` | Wait for event and branch | 1 | 1+ | +| `XOR_GATE` | Branch on condition | 1 | 1+ | +| `XOR_SWITCH_GATE` | Branch on value | 1 | 1+ | diff --git a/doc/code/game_simulation/components.md b/doc/code/game_simulation/components.md deleted file mode 100644 index dc385a368b..0000000000 --- a/doc/code/game_simulation/components.md +++ /dev/null @@ -1,130 +0,0 @@ -# Built-in Components - -Overview of the built-in game entity components in the game simulation. - -1. [Internal](#internal) - 1. [Activity](#activity) - 2. [CommandQueue](#commandqueue) - 3. [Ownership](#ownership) - 4. [Position](#position) -2. [API](#api) - 1. [Idle](#idle) - 2. [Live](#live) - 3. [Move](#move) - 4. [Turn](#turn) - - -## Internal - -Internal components do not have a corresponding nyan API object and thus only -store runtime data. - -### Activity - -![Activity Component UML](images/component_activity_uml.svg) - -The `Activity` component stores a reference to the top-level activity for the -game entity. Essentially, this gives access to the entire activity node graph -used by the entity. - -Additionally, the current activity state is stored on a discrete curve that -contains the last visited node. - -`Activity` also stores the handles of events initiated by the activity system -for advancing to the next node. Once the next node is visited, these events -should be canceled via the `cancel_events(..)` method. - - -### CommandQueue - -![CommandQueue Component UML](images/component_activity_uml.svg) - -The `CommandQueue` component stores commands for the game entity in a [queue curve container](/doc/code/curves.md#queue). - -Commands in the queue use `Command` class derivatives which specify a command type -and payload for the command. - - -### Ownership - -![Ownership Component UML](images/component_ownership_uml.svg) - -The `Ownership` component stores the ID of the player who owns the game entity. - - -### Position - -![Position Component UML](images/component_position_uml.svg) - -The `Position` component stores the location and direction of the game entity -inside the game world. - -The 3D position of the game entity is stored on a continuous curve with value type -`phys3`. - -Directions are stored as angles relative to the camera vector using clock-wise -rotation. Here are some example values for reference to see how that works in -practice: - -| Angle (degrees) | Direction | -| --------------- | --------------------- | -| 0 | look towards camera | -| 90 | look left | -| 180 | look away from camera | -| 270 | look right | - -Angles are stored on a segmented curve. - -## API - -API components have a corresponding nyan API object of type `engine.ability.Ability` defined -in the nyan API. This API object can be retrieved using the `get_ability(..)` method of the -component. - -### Idle - -![Idle Component UML](images/component_idle_uml.svg) - -**nyan API object:** [`engine.ability.type.Idle`](/doc/nyan/api_reference/reference_ability.md#abilitytypeidle) - -The `Idle` component represents the ingame "idle" state of the game entity, i.e. when -it is doing nothing. - -The component stores no runtime data. - - -### Live - -![Live Component UML](images/component_live_uml.svg) - -**nyan API object:** [`engine.ability.type.Live`](/doc/nyan/api_reference/reference_ability.md#abilitytypelive) - -The `Live` component represents the game entity's ability to have attributes (e.g. health). - -An attribute's maximum limit is stored in the nyan API object, while -the game entity's current attribute values are stored in the component -on a discrete curve. - - -### Move - -![Move Component UML](images/component_move_uml.svg) - -**nyan API object:** [`engine.ability.type.Move`](/doc/nyan/api_reference/reference_ability.md#abilitytypemove) - -The `Move` component represents the game entity's ability to move in the game world. -This also allows moving the game entity with move commands. - -The component stores no runtime data. - - -### Turn - -![Turn Component UML](images/component_turn_uml.svg) - -**nyan API object:** [`engine.ability.type.Turn`](/doc/nyan/api_reference/reference_ability.md#abilitytypeturn) - -The `Turn` component represents the game entity's ability to change directions in the game world. -Turning is implicitely required for moving but it also works on its own. - -The component stores no runtime data. diff --git a/doc/code/game_simulation/game_entity.md b/doc/code/game_simulation/game_entity.md index 71f55a8711..e4a146d52c 100644 --- a/doc/code/game_simulation/game_entity.md +++ b/doc/code/game_simulation/game_entity.md @@ -51,8 +51,6 @@ of the specific entity can be accessed via the `GameEntity` object's `get_compon ## Component Data Storage -For a description of the available components, check the [component reference](components.md). - ![Component class UML](images/component_uml.svg) Components are data storage objects for a game entity that also perform the dual role @@ -130,19 +128,6 @@ where paths are taken based on the inputs a game entity receives. The architectu of the activity control flow is described in more detail in the [activity control flow documentation](activity.md). -A game entity's current activity state is stored in its `Activity` component. This component -holds a reference to the activity node graph used by the entity as well as the -last visited node. This node describes which action/behavioural state the -game entity currently is in. - -Advancement to the next node can be initiated in several ways, depending on the -[node type](activity.md#node-types) of the current node. -It can happen automatically or be triggered by an event. In the latter case, -the event is handled by the `GameEntityManager` which calls an activity *system* -that processes the event to choose the next node. - -![Activity Workflow](images/activity_workflow.png) - ### Manager diff --git a/doc/code/game_simulation/images/component_activity_uml.svg b/doc/code/game_simulation/images/component_activity_uml.svg deleted file mode 100644 index c05314eeff..0000000000 --- a/doc/code/game_simulation/images/component_activity_uml.svg +++ /dev/null @@ -1,82 +0,0 @@ - - -NodeActivityActivitystart_activity: Activitynode: curve::Discretescheduled_events: vector<Event>get_start_activity(): Activityget_node(time_t): Nodeset_node(time_t, Node): voidinit(time_t): voidadd_event(Event): voidcancel_events(): void diff --git a/doc/code/game_simulation/images/component_command_queue_uml.svg b/doc/code/game_simulation/images/component_command_queue_uml.svg deleted file mode 100644 index cabb92d98b..0000000000 --- a/doc/code/game_simulation/images/component_command_queue_uml.svg +++ /dev/null @@ -1,51 +0,0 @@ - - -CommandCommandQueuecommand_queue: curve::Queueadd_command(time_t, Command): voidget_queue(): curve::Queue diff --git a/doc/code/game_simulation/images/component_idle_uml.svg b/doc/code/game_simulation/images/component_idle_uml.svg deleted file mode 100644 index 439a7c39af..0000000000 --- a/doc/code/game_simulation/images/component_idle_uml.svg +++ /dev/null @@ -1,25 +0,0 @@ - - -Idle diff --git a/doc/code/game_simulation/images/component_live_uml.svg b/doc/code/game_simulation/images/component_live_uml.svg deleted file mode 100644 index b7d47a963e..0000000000 --- a/doc/code/game_simulation/images/component_live_uml.svg +++ /dev/null @@ -1,33 +0,0 @@ - - -Liveattribute_values: curve::UnorderedMapadd_attribute(time_t, fqon_t, curve::Discrete)set_attribute(time_t, fqon_t, int64_t): void diff --git a/doc/code/game_simulation/images/component_move_uml.svg b/doc/code/game_simulation/images/component_move_uml.svg deleted file mode 100644 index 6076927c78..0000000000 --- a/doc/code/game_simulation/images/component_move_uml.svg +++ /dev/null @@ -1,25 +0,0 @@ - - -Move diff --git a/doc/code/game_simulation/images/component_ownership_uml.svg b/doc/code/game_simulation/images/component_ownership_uml.svg deleted file mode 100644 index 141bd5c7ef..0000000000 --- a/doc/code/game_simulation/images/component_ownership_uml.svg +++ /dev/null @@ -1,33 +0,0 @@ - - -Ownershipowner: curve::Discreteset_owner(time_t, ownership_id_t): voidget_owners(): curve::Discrete diff --git a/doc/code/game_simulation/images/component_position_uml.svg b/doc/code/game_simulation/images/component_position_uml.svg deleted file mode 100644 index 6f090fcb9e..0000000000 --- a/doc/code/game_simulation/images/component_position_uml.svg +++ /dev/null @@ -1,39 +0,0 @@ - - -Positionposition: curve::Continuousangle: curve::Segmentedget_positions(): curve::Continuousget_angles(): curve::Segmentedset_position(time_t, coord::phys3): voidset_angle(time_t, phys_angle_t): void diff --git a/doc/code/game_simulation/images/component_turn_uml.svg b/doc/code/game_simulation/images/component_turn_uml.svg deleted file mode 100644 index 9f06b9f662..0000000000 --- a/doc/code/game_simulation/images/component_turn_uml.svg +++ /dev/null @@ -1,25 +0,0 @@ - - -Turn diff --git a/libopenage/gamestate/component/api/apply_effect.h b/libopenage/gamestate/component/api/apply_effect.h index 0411a5abc2..c9a709d3ea 100644 --- a/libopenage/gamestate/component/api/apply_effect.h +++ b/libopenage/gamestate/component/api/apply_effect.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -11,15 +11,30 @@ namespace openage::gamestate::component { /** - * Component for ApplyEffect abilities. + * Stores runtime information for an ApplyEffect ability of a game entity. */ class ApplyEffect final : public APIComponent { public: + /** + * Creates an ApplyEffect component. + * + * @param loop Event loop that all events from the component are registered on. + * @param ability nyan ability object for the component. + * @param creation_time Ingame creation time of the component. + * @param enabled If true, enable the component at creation time. + */ ApplyEffect(const std::shared_ptr &loop, nyan::Object &ability, const time::time_t &creation_time, bool enabled = true); + /** + * Creates an ApplyEffect component. + * + * @param loop Event loop that all events from the component are registered on. + * @param ability nyan ability object for the component. + * @param enabled If true, enable the component at creation time. + */ ApplyEffect(const std::shared_ptr &loop, nyan::Object &ability, bool enabled = true); @@ -27,9 +42,12 @@ class ApplyEffect final : public APIComponent { component_t get_type() const override; /** - * Get the last initiaton time that is before the given \p time. + * Get the last time an effect application was initiated that is before the given \p time. * - * @param time Current simulation time. + * This should be used to determine if the application of the effect is still + * active and when the next application can be initiated. + * + * @param time Simulation time. * * @return Curve with the last initiation times. */ @@ -38,23 +56,28 @@ class ApplyEffect final : public APIComponent { /** * Get the last time the effects were applied before the given \p time. * - * @param time Current simulation time. + * This should be used to determine if the effects are under a cooldown, i.e. + * to check if the effects can be applied again. + * + * @param time 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. + * Record the simulation time when the entity initiates an effect application, + * i.e. it starts using the ability. * - * @param time Time at which the entity initiates using the ability. + * @param time Time at which the entity starts using the ability. */ void set_init_time(const time::time_t &time); /** - * Record the simulation time when the entity last applied the effects. + * Record the simulation time when the entity applies the effects + * of the ability, i.e. init time + application delay. * - * @param time Time at which the entity last applied the effects. + * @param time Time at which the entity applies the effects. */ void set_last_used(const time::time_t &time); diff --git a/libopenage/gamestate/component/api/idle.h b/libopenage/gamestate/component/api/idle.h index e8b114ff67..ce10a7128b 100644 --- a/libopenage/gamestate/component/api/idle.h +++ b/libopenage/gamestate/component/api/idle.h @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -7,6 +7,10 @@ namespace openage::gamestate::component { +/** + * Represents an idle state of a game entity, i.e. when it is not + * performing any action or command. + */ class Idle final : public APIComponent { public: using APIComponent::APIComponent; diff --git a/libopenage/gamestate/component/api/line_of_sight.h b/libopenage/gamestate/component/api/line_of_sight.h index f2d937ed01..373ed039e5 100644 --- a/libopenage/gamestate/component/api/line_of_sight.h +++ b/libopenage/gamestate/component/api/line_of_sight.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,6 +8,9 @@ namespace openage::gamestate::component { +/** + * Stores the line of sight information of a game entity. + */ class LineOfSight final : public APIComponent { public: using APIComponent::APIComponent; diff --git a/libopenage/gamestate/component/api/live.h b/libopenage/gamestate/component/api/live.h index 601afe4578..6be45a8bfc 100644 --- a/libopenage/gamestate/component/api/live.h +++ b/libopenage/gamestate/component/api/live.h @@ -21,6 +21,12 @@ class Segmented; } // namespace curve namespace gamestate::component { + +/** + * Stores runtime information for a Live ability of a game entity. + * + * Represents the ability of a game entity to have attributes, e.g. health, faith, etc. + */ class Live final : public APIComponent { public: using APIComponent::APIComponent; diff --git a/libopenage/gamestate/component/api/move.h b/libopenage/gamestate/component/api/move.h index 99d87b8d72..01e773cc72 100644 --- a/libopenage/gamestate/component/api/move.h +++ b/libopenage/gamestate/component/api/move.h @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,6 +8,9 @@ namespace openage::gamestate::component { +/** + * Stores the movement information of a game entity. + */ class Move final : public APIComponent { public: using APIComponent::APIComponent; diff --git a/libopenage/gamestate/component/api/resistance.h b/libopenage/gamestate/component/api/resistance.h index 78dba25f87..c73468534d 100644 --- a/libopenage/gamestate/component/api/resistance.h +++ b/libopenage/gamestate/component/api/resistance.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,6 +8,12 @@ namespace openage::gamestate::component { +/** + * Stores information about the resistances of a game entity. + * + * Used together with the ApplyEffect component to allow interactions + * between game entities via effects. + */ class Resistance final : public APIComponent { public: using APIComponent::APIComponent; diff --git a/libopenage/gamestate/component/api/selectable.h b/libopenage/gamestate/component/api/selectable.h index b37f674444..249172f277 100644 --- a/libopenage/gamestate/component/api/selectable.h +++ b/libopenage/gamestate/component/api/selectable.h @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #pragma once @@ -10,6 +10,9 @@ namespace openage::gamestate::component { +/** + * Represents the ability of a game entity to be selected. + */ class Selectable final : public APIComponent { public: using APIComponent::APIComponent; diff --git a/libopenage/gamestate/component/api/turn.h b/libopenage/gamestate/component/api/turn.h index 9506bcd6db..47c5b62528 100644 --- a/libopenage/gamestate/component/api/turn.h +++ b/libopenage/gamestate/component/api/turn.h @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -10,6 +10,9 @@ namespace openage::gamestate::component { +/** + * Represents the ability of a game entity to change directions. + */ class Turn final : public APIComponent { public: using APIComponent::APIComponent; diff --git a/libopenage/gamestate/component/internal/activity.h b/libopenage/gamestate/component/internal/activity.h index ae0fd73389..d6fed0e176 100644 --- a/libopenage/gamestate/component/internal/activity.h +++ b/libopenage/gamestate/component/internal/activity.h @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -27,23 +27,26 @@ class Node; namespace component { +/** + * Store the activity flow graph of a game entity. + */ class Activity final : public InternalComponent { public: /** * Creates a new activity component. * * @param loop Event loop that all events from the component are registered on. - * @param start_activity Initial activity flow graph. + * @param activity Activity flow graph. */ Activity(const std::shared_ptr &loop, - const std::shared_ptr &start_activity); + const std::shared_ptr &activity); component_t get_type() const override; /** - * Get the initial activity. + * Get the activity graph of the component. * - * @return Initial activity. + * @return Activity graph. */ const std::shared_ptr &get_start_activity() const; @@ -51,35 +54,39 @@ class Activity final : public InternalComponent { * Get the node in the activity flow graph at a given time. * * @param time Time at which the node is requested. - * @return Current node in the flow graph. + * + * @return Node in the flow graph. */ const std::shared_ptr get_node(const time::time_t &time) const; /** - * Sets the current node in the activity flow graph at a given time. + * Sets the node in the activity flow graph at a given time. * * @param time Time at which the node is set. - * @param node Current node in the flow graph. + * @param node Node in the flow graph. */ void set_node(const time::time_t &time, const std::shared_ptr &node); /** - * Set the current node to the start node of the start activity. + * Initialize the activity flow graph for the component at a given time. * - * @param time Time at which the node is set. + * This sets the current node at \p time to the start node of the activity graph. + * + * @param time Time at which the component is initialized. */ void init(const time::time_t &time); /** - * Add a scheduled event that is waited for to progress in the node graph. + * Store a scheduled event that the activity system waits for to + * progress in the node graph. * * @param event Event to add. */ void add_event(const std::shared_ptr &event); /** - * Cancel all scheduled events. + * Cancel all stored scheduled events. * * @param time Time at which the events are cancelled. */ @@ -87,7 +94,7 @@ class Activity final : public InternalComponent { private: /** - * Initial activity that encapsulates the entity's control flow graph. + * Activity that encapsulates the entity's control flow graph. * * When a game entity is spawned, the activity system should advance in * this activity flow graph to initialize the entity's action state. @@ -97,12 +104,12 @@ class Activity final : public InternalComponent { std::shared_ptr start_activity; /** - * Current node in the activity flow graph. + * Current active node in the activity flow graph over time. */ curve::Discrete> node; /** - * Scheduled events that are waited for to progress in the node graph. + * Scheduled events that the actvity system waits for. */ std::vector> scheduled_events; }; diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index 440a6a0eea..45a9ec39d1 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -23,6 +23,9 @@ class EventLoop; namespace gamestate::component { +/** + * Stores commands for a game entity. + */ class CommandQueue final : public InternalComponent { public: /** @@ -110,6 +113,8 @@ class CommandQueue final : public InternalComponent { private: /** * Command queue. + * + * Stores the commands received by the entity over time. */ curve::Queue> command_queue; }; diff --git a/libopenage/gamestate/component/internal/ownership.h b/libopenage/gamestate/component/internal/ownership.h index ab4b30bed3..2d2f52ed68 100644 --- a/libopenage/gamestate/component/internal/ownership.h +++ b/libopenage/gamestate/component/internal/ownership.h @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -19,6 +19,9 @@ class EventLoop; namespace gamestate::component { +/** + * Stores ownership information of a game entity. + */ class Ownership final : public InternalComponent { public: /** @@ -58,7 +61,7 @@ class Ownership final : public InternalComponent { private: /** - * Owner ID storage over time. + * ID of the entity owner over time. */ curve::Discrete owner; }; diff --git a/libopenage/gamestate/component/internal/position.h b/libopenage/gamestate/component/internal/position.h index ff4cab0da2..bb79c21013 100644 --- a/libopenage/gamestate/component/internal/position.h +++ b/libopenage/gamestate/component/internal/position.h @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -20,6 +20,11 @@ class EventLoop; namespace gamestate::component { +/** + * Stores positional information about a game entity, i.e. location and + * direction. + * + */ class Position final : public InternalComponent { public: /** From 6ff88b98869756cd7a888a57963335aab2e74803 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 21:41:47 +0200 Subject: [PATCH 108/118] cfg: Update modpack version of converted games. --- cfg/converter/games/game_editions.toml | 28 +++++++++++++------------- openage/convert/tool/api_export.py | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cfg/converter/games/game_editions.toml b/cfg/converter/games/game_editions.toml index 24628e8273..368bc6079e 100644 --- a/cfg/converter/games/game_editions.toml +++ b/cfg/converter/games/game_editions.toml @@ -38,9 +38,9 @@ expansions = [] ] [AOC.targetmods.aoe2_base] - version = "0.5.1" + version = "0.6.0" versionstr = "1.0c" - min_api_version = "0.5.0" + min_api_version = "0.6.0" [AOCDEMO] @@ -63,9 +63,9 @@ expansions = [] blend = ["data/blendomatic.dat"] [AOCDEMO.targetmods.trial_base] - version = "0.5.1" + version = "0.6.0" versionstr = "Trial" - min_api_version = "0.5.0" + min_api_version = "0.6.0" [AOK] @@ -145,9 +145,9 @@ expansions = [] ] [AOE1DE.targetmods.de1_base] - version = "0.5.1" + version = "0.6.0" versionstr = "1.0a" - min_api_version = "0.5.0" + min_api_version = "0.6.0" [ROR] @@ -185,9 +185,9 @@ expansions = [] ] [ROR.targetmods.aoe1_base] - version = "0.5.1" + version = "0.6.0" versionstr = "1.0a" - min_api_version = "0.5.0" + min_api_version = "0.6.0" [HDEDITION] @@ -229,9 +229,9 @@ expansions = [] ] [HDEDITION.targetmods.hd_base] - version = "0.5.1" + version = "0.6.0" versionstr = "5.8" - min_api_version = "0.5.0" + min_api_version = "0.6.0" [AOE2DE] @@ -278,9 +278,9 @@ expansions = [] ] [AOE2DE.targetmods.de2_base] - version = "0.6.0" + version = "0.7.0" versionstr = "Update 118476+" - min_api_version = "0.5.0" + min_api_version = "0.6.0" [SWGB] @@ -320,6 +320,6 @@ expansions = ["SWGB_CC"] ] [SWGB.targetmods.swgb_base] - version = "0.5.1" + version = "0.6.0" versionstr = "1.1-gog4" - min_api_version = "0.5.0" + min_api_version = "0.6.0" diff --git a/openage/convert/tool/api_export.py b/openage/convert/tool/api_export.py index b20752e330..e3a82ee888 100644 --- a/openage/convert/tool/api_export.py +++ b/openage/convert/tool/api_export.py @@ -1,4 +1,4 @@ -# Copyright 2023-2024 the openage authors. See copying.md for legal info. +# Copyright 2023-2025 the openage authors. See copying.md for legal info. """ Export tool for dumping the nyan API of the engine from the converter. @@ -75,7 +75,7 @@ def create_modpack() -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("engine", modpack_version="0.5.0", versionstr="0.5.0", repo="openage") + mod_def.set_info("engine", modpack_version="0.6.0", versionstr="0.6.0", repo="openage") mod_def.add_include("**") From 3a197e4ec104a2f4406023e62dce8eb3fa2122ad Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 21:59:22 +0200 Subject: [PATCH 109/118] convert: Fix semantic version comparison. --- openage/game/main.py | 3 ++- openage/main/main.py | 3 ++- openage/util/version.py | 32 ++++++++++++++++++-------------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/openage/game/main.py b/openage/game/main.py index 1ad1378426..4f40e4fdae 100644 --- a/openage/game/main.py +++ b/openage/game/main.py @@ -1,4 +1,4 @@ -# Copyright 2015-2024 the openage authors. See copying.md for legal info. +# Copyright 2015-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals @@ -93,6 +93,7 @@ def main(args, error): # ensure that the openage API is present if api_export_required(asset_path): # export to assets folder + info("Updating outdated nyan API modpack") converted_path = asset_path / "converted" converted_path.mkdirs() export_api(converted_path) diff --git a/openage/main/main.py b/openage/main/main.py index 05266ec744..50f1a7ba75 100644 --- a/openage/main/main.py +++ b/openage/main/main.py @@ -1,4 +1,4 @@ -# Copyright 2015-2024 the openage authors. See copying.md for legal info. +# Copyright 2015-2025 the openage authors. See copying.md for legal info. """ Main engine entry point for openage. @@ -88,6 +88,7 @@ def main(args, error): # ensure that the openage API is present if api_export_required(asset_path): # export to assets folder + info("Updating outdated nyan API modpack") converted_path = asset_path / "converted" converted_path.mkdirs() export_api(converted_path) diff --git a/openage/util/version.py b/openage/util/version.py index 712b04ec79..a9a7ffd630 100644 --- a/openage/util/version.py +++ b/openage/util/version.py @@ -1,4 +1,4 @@ -# Copyright 2024-2024 the openage authors. See copying.md for legal info. +# Copyright 2024-2025 the openage authors. See copying.md for legal info. """ Handling of version information for openage. @@ -36,26 +36,30 @@ def __init__(self, version: str) -> None: self.buildmetadata = match.group("buildmetadata") def __lt__(self, other: SemanticVersion) -> bool: - if self.major < other.major: - return True - if self.minor < other.minor: - return True - if self.patch < other.patch: - return True + if self.major != other.major: + return self.major < other.major + if self.minor != other.minor: + return self.minor < other.minor + + if self.patch != other.patch: + return self.patch < other.patch + + # else: versions are equal return False def __le__(self, other: SemanticVersion) -> bool: - if self.major <= other.major: - return True + if self.major != other.major: + return self.major < other.major - if self.minor <= other.minor: - return True + if self.minor != other.minor: + return self.minor < other.minor - if self.patch <= other.patch: - return True + if self.patch != other.patch: + return self.patch < other.patch - return False + # else: versions are equal + return True def __eq__(self, other: SemanticVersion) -> bool: return (self.major == other.major and From 88268854ddf68e0c6746c6f1e0517fcbb7fb83f3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Aug 2025 01:42:56 +0200 Subject: [PATCH 110/118] curve: Add unit tests to check compressed curve size and content. --- libopenage/curve/tests/curve_types.cpp | 56 +++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/libopenage/curve/tests/curve_types.cpp b/libopenage/curve/tests/curve_types.cpp index 3a0ecb9b0e..2afdc2138d 100644 --- a/libopenage/curve/tests/curve_types.cpp +++ b/libopenage/curve/tests/curve_types.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include #include @@ -245,6 +245,11 @@ void curve_types() { c.set_insert(6, 4); c.set_insert(7, 4); + auto size_before = c.get_container().size(); + // 1 initial element at -TIME_MIN + // 8 inserted elements + TESTEQUALS(size_before, 9); + auto frame0 = c.frame(2); TESTEQUALS(frame0.first, 2); TESTEQUALS(frame0.second, 2); @@ -255,8 +260,14 @@ void curve_types() { TESTEQUALS(frame1.second, 3); TESTEQUALS(c.get(4), 3); + // Compress the curve after t = 0 c.compress(0); + auto size_after = c.get_container().size(); + // 1 initial element at -TIME_MIN + // 5 inserted elements + TESTEQUALS(size_after, 6); + auto frame2 = c.frame(2); TESTEQUALS(frame2.first, 0); TESTEQUALS(frame2.second, 0); @@ -266,6 +277,23 @@ void curve_types() { TESTEQUALS(frame3.first, 3); TESTEQUALS(frame3.second, 3); TESTEQUALS(c.get(4), 3); + + auto expected_keyframes = std::vector>>{ + {time::TIME_MIN, 0}, + {0, 0}, + {3, 3}, + {5, 3}, + {6, 4}, + {7, 4}, + }; + TESTEQUALS(c.get_container().size(), expected_keyframes.size()); + + auto idx = 0; + for (auto keyframe : c.get_container()) { + TESTEQUALS(keyframe.time(), expected_keyframes[idx].time()); + TESTEQUALS(keyframe.val(), expected_keyframes[idx].val()); + ++idx; + } } // Check the discrete type @@ -304,6 +332,11 @@ void curve_types() { c.set_insert(4, 4); c.set_insert(5, 4); // redundant + auto size_before = c.get_container().size(); + // 1 initial element at -TIME_MIN + // 6 inserted elements + TESTEQUALS(size_before, 7); + auto frame0 = c.frame(2); TESTEQUALS(frame0.first, 2); TESTEQUALS(frame0.second, 3); @@ -314,8 +347,14 @@ void curve_types() { TESTEQUALS(frame1.second, 4); TESTEQUALS(c.get(5), 4); + // Compress the curve after t = 0 c.compress(0); + auto size_after = c.get_container().size(); + // 1 initial element at -TIME_MIN + // 3 inserted elements + TESTEQUALS(size_after, 4); + auto frame2 = c.frame(2); TESTEQUALS(frame2.first, 1); TESTEQUALS(frame2.second, 3); @@ -325,6 +364,21 @@ void curve_types() { TESTEQUALS(frame3.first, 4); TESTEQUALS(frame3.second, 4); TESTEQUALS(c.get(5), 4); + + auto expected_keyframes = std::vector>{ + {time::TIME_MIN, 0}, + {0, 1}, + {1, 3}, + {4, 4}, + }; + TESTEQUALS(c.get_container().size(), expected_keyframes.size()); + + auto idx = 0; + for (auto keyframe : c.get_container()) { + TESTEQUALS(keyframe.time(), expected_keyframes[idx].time()); + TESTEQUALS(keyframe.val(), expected_keyframes[idx].val()); + ++idx; + } } // Check the discrete mod type From 3f186744c15535bb29c6d651f4523a1922601be2 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Aug 2025 01:50:32 +0200 Subject: [PATCH 111/118] curve: Remove explicit passing of container size in sync() of KeyframeContainer. --- libopenage/curve/keyframe_container.h | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index fc909071e1..c74c1f28d3 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -330,28 +330,28 @@ class KeyframeContainer { /** * Erase all elements after the given element. - * - * @param last_valid Location of the last element to keep. - * - * @return Location (index) of the last element that was kept. + * + * @param last_valid Location of the last element to keep. + * + * @return Location (index) of the last element that was kept. */ elem_ptr erase_after(elem_ptr last_valid); /** * Erase a single element from the container. - * - * @param it Location of the element to erase. - * - * @return Location (index) of the next element after the erased one. + * + * @param it Location of the element to erase. + * + * @return Location (index) of the next element after the erased one. */ elem_ptr erase(elem_ptr it); /** * Erase all elements with given time. Starts the search at the end of the container. * - * @param time Time of the elements to erase. - * - * @return Location (index) of the next element after the erased one. + * @param time Time of the elements to erase. + * + * @return Location (index) of the next element after the erased one. */ elem_ptr erase(const time::time_t &time) { return this->erase(time, this->container.size()); @@ -361,9 +361,9 @@ class KeyframeContainer { * Erase all element with given time. Include a hint where to start the search. * * @param time Time of the elements to erase. - * @param hint Index of the approximate element location. - * - * @return Location (index) of the next element after the erased one. + * @param hint Index of the approximate element location. + * + * @return Location (index) of the next element after the erased one. */ elem_ptr erase(const time::time_t &time, const elem_ptr &hint) { @@ -633,7 +633,7 @@ KeyframeContainer::sync(const KeyframeContainer &other, const std::function &converter, const time::time_t &start) { // Delete elements after start time - elem_ptr at = this->last_before(start, this->container.size()); + elem_ptr at = this->last_before(start); at = this->erase_after(at); // Find the last element before the start time in the other container From 04ab45277d9104aba905d8c27fc4678b94eeab1b Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Aug 2025 02:14:54 +0200 Subject: [PATCH 112/118] gamestate: Return NONE command if command queue is empty on switch condition. --- libopenage/gamestate/activity/condition/next_command_switch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libopenage/gamestate/activity/condition/next_command_switch.cpp b/libopenage/gamestate/activity/condition/next_command_switch.cpp index 33626c4338..165ab5604e 100644 --- a/libopenage/gamestate/activity/condition/next_command_switch.cpp +++ b/libopenage/gamestate/activity/condition/next_command_switch.cpp @@ -16,7 +16,7 @@ int next_command_switch(const time::time_t &time, entity->get_component(component::component_t::COMMANDQUEUE)); if (command_queue->get_commands().empty(time)) { - return -1; + return static_cast(component::command::command_t::NONE); } auto command = command_queue->get_commands().front(time); From 65bde3095ab2dac5fc33ff78f5284d7f5404c44e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Aug 2025 03:06:01 +0200 Subject: [PATCH 113/118] gamestate: Use absolute difference for calculating angles. --- libopenage/gamestate/system/apply_effect.cpp | 10 +++------- libopenage/gamestate/system/move.cpp | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index 1cebbbc7e8..0fb6831000 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -67,15 +67,11 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptris_infinite_positive()) { - auto angle_diff = path_angle - effector_angle; - if (angle_diff < 0) { - // get the positive difference - angle_diff = angle_diff * -1; - } + auto angle_diff = path_angle.abs_diff(effector_angle); + if (angle_diff > 180) { // always use the smaller angle - angle_diff = angle_diff - 360; - angle_diff = angle_diff * -1; + angle_diff = coord::phys_angle_t::same_type_but_unsigned{360} - angle_diff; } double turn_time = angle_diff.to_double() / turn_speed->get(); diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index 7229305ae2..eda99b4734 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -171,15 +171,11 @@ const time::time_t Move::move_default(const std::shared_ptris_infinite_positive()) { - auto angle_diff = path_angle - current_angle; - if (angle_diff < 0) { - // get the positive difference - angle_diff = angle_diff * -1; - } + auto angle_diff = path_angle.abs_diff(current_angle); + if (angle_diff > 180) { // always use the smaller angle - angle_diff = angle_diff - 360; - angle_diff = angle_diff * -1; + angle_diff = coord::phys_angle_t::same_type_but_unsigned{360} - angle_diff; } // Set an intermediate position keyframe to halt the game entity From 6742098953aba1c440a73792af232ab0180b5c36 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Aug 2025 03:40:35 +0200 Subject: [PATCH 114/118] gamestate: Use std::visit to process move target variant. --- libopenage/gamestate/system/move.cpp | 44 ++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index eda99b4734..f69035a931 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -34,6 +34,12 @@ namespace openage::gamestate::system { +// helper type for the visitor processing the move_target +template +struct overloaded : Ts... { + using Ts::operator()...; +}; + std::vector find_path(const std::shared_ptr &pathfinder, path::grid_id_t grid_id, @@ -104,24 +110,26 @@ const time::time_t Move::move_target(const std::shared_ptr(target.value())) { - auto target_id = std::get(target.value()); - auto target_entity = state->get_game_entity(target_id); - - auto position = std::dynamic_pointer_cast( - target_entity->get_component(component::component_t::POSITION)); - - auto target_pos = position->get_positions().get(start_time); - - return Move::move_default(entity, state, target_pos, start_time); - } - else if (std::holds_alternative(target.value())) { - auto target_pos = std::get(target.value()); - return Move::move_default(entity, state, target_pos, start_time); - } - - log::log(WARN << "Entity " << entity->get_id() << " has an invalid target at time " << start_time); - return time::time_t::from_int(0); + return std::visit( + overloaded{ + [&](const gamestate::entity_id_t &target_id) { + auto target_entity = state->get_game_entity(target_id); + + auto position = std::dynamic_pointer_cast( + target_entity->get_component(component::component_t::POSITION)); + + auto target_pos = position->get_positions().get(start_time); + + return Move::move_default(entity, state, target_pos, start_time); + }, + [&](const coord::phys3 &target_pos) { + return Move::move_default(entity, state, target_pos, start_time); + }, + [&](const auto &target) { + log::log(WARN << "Entity " << entity->get_id() << " has an invalid target type: " + << typeid(target).name() << " at time " << start_time); + return time::time_t::from_int(0); }}, + target.value()); } From c7d9b6f65dd061694a04c7aa85a859123514535f Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Aug 2025 15:04:24 +0200 Subject: [PATCH 115/118] gamestate: Use nyan Object::extends to check for parent relationship. --- libopenage/gamestate/api/ability.cpp | 8 +------- libopenage/gamestate/api/activity.cpp | 16 ++-------------- libopenage/gamestate/api/animation.cpp | 8 +------- libopenage/gamestate/api/effect.cpp | 8 +------- libopenage/gamestate/api/object.cpp | 10 ++-------- libopenage/gamestate/api/patch.cpp | 8 +------- libopenage/gamestate/api/player_setup.cpp | 8 +------- libopenage/gamestate/api/property.cpp | 8 +------- libopenage/gamestate/api/resistance.cpp | 8 +------- libopenage/gamestate/api/sound.cpp | 8 +------- libopenage/gamestate/api/terrain.cpp | 8 +------- 11 files changed, 13 insertions(+), 85 deletions(-) diff --git a/libopenage/gamestate/api/ability.cpp b/libopenage/gamestate/api/ability.cpp index a45d8e3afc..5444c34b2f 100644 --- a/libopenage/gamestate/api/ability.cpp +++ b/libopenage/gamestate/api/ability.cpp @@ -17,13 +17,7 @@ namespace openage::gamestate::api { bool APIAbility::is_ability(const nyan::Object &obj) { - for (auto &parent : obj.get_parents()) { - if (parent == "engine.ability.Ability") { - return true; - } - } - - return false; + return obj.extends("engine.ability.Ability"); } bool APIAbility::check_type(const nyan::Object &ability, diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index 535cfea1ca..c644c1c8c4 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -9,13 +9,7 @@ namespace openage::gamestate::api { bool APIActivity::is_activity(const nyan::Object &obj) { - for (auto &parent : obj.get_parents()) { - if (parent == "engine.util.activity.Activity") { - return true; - } - } - - return false; + return obj.extends("engine.util.activity.Activity"); } nyan::Object APIActivity::get_start(const nyan::Object &activity) { @@ -27,13 +21,7 @@ nyan::Object APIActivity::get_start(const nyan::Object &activity) { bool APIActivityNode::is_node(const nyan::Object &obj) { - for (auto &parent : obj.get_parents()) { - if (parent == "engine.util.activity.node.Node") { - return true; - } - } - - return false; + return obj.extends("engine.util.activity.node.Node"); } activity::node_t APIActivityNode::get_type(const nyan::Object &node) { diff --git a/libopenage/gamestate/api/animation.cpp b/libopenage/gamestate/api/animation.cpp index a8e62e4f9d..100ad881dd 100644 --- a/libopenage/gamestate/api/animation.cpp +++ b/libopenage/gamestate/api/animation.cpp @@ -10,13 +10,7 @@ namespace openage::gamestate::api { bool APIAnimation::is_animation(nyan::Object &obj) { - for (auto &parent : obj.get_parents()) { - if (parent == "engine.util.animation.Animation") { - return true; - } - } - - return false; + return obj.extends("engine.util.animation.Animation"); } const std::string APIAnimation::get_animation_path(const nyan::Object &animation) { diff --git a/libopenage/gamestate/api/effect.cpp b/libopenage/gamestate/api/effect.cpp index e5b566fe7a..f493a08075 100644 --- a/libopenage/gamestate/api/effect.cpp +++ b/libopenage/gamestate/api/effect.cpp @@ -9,13 +9,7 @@ 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; + return obj.extends("engine.effect.Effect"); } bool APIEffect::check_type(const nyan::Object &effect, diff --git a/libopenage/gamestate/api/object.cpp b/libopenage/gamestate/api/object.cpp index 56d8cbbafc..be2c0eed1e 100644 --- a/libopenage/gamestate/api/object.cpp +++ b/libopenage/gamestate/api/object.cpp @@ -8,13 +8,7 @@ namespace openage::gamestate::api { bool APIObject::is_object(const nyan::Object &obj) { - for (const auto &parent : obj.get_parents()) { - if (parent == "engine.root.Object") { - return true; - } - } - - return false; + return obj.extends("engine.root.Object"); } -} // namespace +} // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/patch.cpp b/libopenage/gamestate/api/patch.cpp index b390c48b9a..8fb4544f2d 100644 --- a/libopenage/gamestate/api/patch.cpp +++ b/libopenage/gamestate/api/patch.cpp @@ -16,13 +16,7 @@ namespace openage::gamestate::api { bool APIPatch::is_patch(const nyan::Object &obj) { - for (auto &parent : obj.get_parents()) { - if (parent == "engine.util.patch.Patch") { - return true; - } - } - - return false; + return obj.extends("engine.util.patch.Patch"); } bool APIPatch::check_property(const nyan::Object &patch, diff --git a/libopenage/gamestate/api/player_setup.cpp b/libopenage/gamestate/api/player_setup.cpp index d290c75633..3c685a9456 100644 --- a/libopenage/gamestate/api/player_setup.cpp +++ b/libopenage/gamestate/api/player_setup.cpp @@ -13,13 +13,7 @@ namespace openage::gamestate::api { bool APIPlayerSetup::is_player_setup(const nyan::Object &obj) { - for (auto &parent : obj.get_parents()) { - if (parent == "engine.util.setup.PlayerSetup") { - return true; - } - } - - return false; + return obj.extends("engine.util.setup.PlayerSetup"); } const std::vector APIPlayerSetup::get_modifiers(const nyan::Object &player_setup) { diff --git a/libopenage/gamestate/api/property.cpp b/libopenage/gamestate/api/property.cpp index c4005ad6a0..e9af743b5b 100644 --- a/libopenage/gamestate/api/property.cpp +++ b/libopenage/gamestate/api/property.cpp @@ -13,13 +13,7 @@ namespace openage::gamestate::api { bool APIAbilityProperty::is_property(const nyan::Object &obj) { - for (auto &parent : obj.get_parents()) { - if (parent == "engine.ability.property.Property") { - return true; - } - } - - return false; + return obj.extends("engine.ability.property.Property"); } const std::vector APIAbilityProperty::get_animations(const nyan::Object &property) { diff --git a/libopenage/gamestate/api/resistance.cpp b/libopenage/gamestate/api/resistance.cpp index b892856dfb..8bce0dc29a 100644 --- a/libopenage/gamestate/api/resistance.cpp +++ b/libopenage/gamestate/api/resistance.cpp @@ -9,13 +9,7 @@ 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; + return obj.extends("engine.resistance.Resistance"); } bool APIResistance::check_effect_type(const nyan::Object &resistance, diff --git a/libopenage/gamestate/api/sound.cpp b/libopenage/gamestate/api/sound.cpp index 9e3b7947c7..609d1a2070 100644 --- a/libopenage/gamestate/api/sound.cpp +++ b/libopenage/gamestate/api/sound.cpp @@ -12,13 +12,7 @@ namespace openage::gamestate::api { bool APISound::is_sound(const nyan::Object &obj) { - for (auto &parent : obj.get_parents()) { - if (parent == "engine.util.sound.Sound") { - return true; - } - } - - return false; + return obj.extends("engine.util.sound.Sound"); } const std::string APISound::get_sound_path(const nyan::Object &sound) { diff --git a/libopenage/gamestate/api/terrain.cpp b/libopenage/gamestate/api/terrain.cpp index 53e2be027d..5a787bf9f9 100644 --- a/libopenage/gamestate/api/terrain.cpp +++ b/libopenage/gamestate/api/terrain.cpp @@ -10,13 +10,7 @@ namespace openage::gamestate::api { bool APITerrain::is_terrain(const nyan::Object &obj) { - for (auto &parent : obj.get_parents()) { - if (parent == "engine.util.terrain.Terrain") { - return true; - } - } - - return false; + return obj.extends("engine.util.terrain.Terrain"); } const std::string APITerrain::get_terrain_path(const nyan::Object &terrain) { From b0d6eb46671cd15fc58d2fe7213a7df6b461ca60 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Aug 2025 16:46:05 +0200 Subject: [PATCH 116/118] Let nyan handle casting of ValueHolder to Value. --- libopenage/gamestate/api/ability.cpp | 6 ++---- libopenage/gamestate/api/activity.cpp | 10 +++++----- libopenage/gamestate/api/effect.cpp | 6 ++---- libopenage/gamestate/api/patch.cpp | 3 +-- libopenage/gamestate/api/player_setup.cpp | 6 +++--- libopenage/gamestate/api/property.cpp | 8 ++++---- libopenage/gamestate/api/resistance.cpp | 6 ++---- libopenage/gamestate/api/terrain.cpp | 4 ++-- libopenage/gamestate/entity_factory.cpp | 10 +++++----- libopenage/gamestate/system/apply_effect.cpp | 6 +++--- 10 files changed, 29 insertions(+), 36 deletions(-) diff --git a/libopenage/gamestate/api/ability.cpp b/libopenage/gamestate/api/ability.cpp index 5444c34b2f..49b9ef4244 100644 --- a/libopenage/gamestate/api/ability.cpp +++ b/libopenage/gamestate/api/ability.cpp @@ -25,8 +25,7 @@ bool APIAbility::check_type(const nyan::Object &ability, nyan::fqon_t api_parent = get_api_parent(ability); nyan::ValueHolder ability_type = ABILITY_DEFS.get(type); - std::shared_ptr ability_val = std::dynamic_pointer_cast( - ability_type.get_ptr()); + auto ability_val = ability_type.get_value_ptr(); return ability_val->get_name() == api_parent; } @@ -61,8 +60,7 @@ const nyan::Object APIAbility::get_property(const nyan::Object &ability, const a nyan::ValueHolder property_type = ABILITY_PROPERTY_DEFS.get(property); std::shared_ptr db_view = ability.get_view(); - std::shared_ptr property_val = std::dynamic_pointer_cast( - properties->get().at(property_type).get_ptr()); + auto property_val = properties->get().at(property_type).get_value_ptr(); return db_view->get_object(property_val->get_name()); } diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index c644c1c8c4..a4f7b6407d 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -67,7 +67,7 @@ std::vector APIActivityNode::get_next(const nyan::Object &node) { std::vector next_nodes; for (auto &condition : conditions->get()) { - auto condition_value = std::dynamic_pointer_cast(condition.get_ptr()); + auto condition_value = condition.get_value_ptr(); auto condition_obj = db_view->get_object(condition_value->get_name()); auto next_node_value = condition_obj.get("Condition.next"); @@ -85,7 +85,7 @@ std::vector APIActivityNode::get_next(const nyan::Object &node) { std::vector next_nodes; for (auto &next_node : next->get()) { - auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); + auto next_node_value = next_node.second.get_value_ptr(); next_nodes.push_back(db_view->get_object(next_node_value->get_name())); } @@ -104,7 +104,7 @@ std::vector APIActivityNode::get_next(const nyan::Object &node) { 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()); + auto next_node_value = next_node.second.get_value_ptr(); next_nodes.push_back(db_view->get_object(next_node_value->get_name())); } @@ -179,14 +179,14 @@ APIActivitySwitchCondition::lookup_map_t APIActivitySwitchCondition::get_lookup_ auto next = condition.get("NextCommand.next"); lookup_map_t lookup_map{}; for (auto next_node : next->get()) { - auto key_value = std::dynamic_pointer_cast(next_node.first.get_ptr()); + auto key_value = next_node.first.get_value_ptr(); auto key_obj = condition.get_view()->get_object(key_value->get_name()); // Get engine lookup key value auto key = static_cast(COMMAND_LOOKUP.get(key_obj.get_name())); // Get node ID - auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); + auto next_node_value = next_node.second.get_value_ptr(); auto next_node_id = next_node_value->get_name(); lookup_map[key] = next_node_id; diff --git a/libopenage/gamestate/api/effect.cpp b/libopenage/gamestate/api/effect.cpp index f493a08075..1cd62afbbb 100644 --- a/libopenage/gamestate/api/effect.cpp +++ b/libopenage/gamestate/api/effect.cpp @@ -17,8 +17,7 @@ bool APIEffect::check_type(const nyan::Object &effect, nyan::fqon_t api_parent = get_api_parent(effect); nyan::ValueHolder effect_type = EFFECT_DEFS.get(type); - std::shared_ptr effect_val = std::dynamic_pointer_cast( - effect_type.get_ptr()); + auto effect_val = effect_type.get_value_ptr(); return effect_val->get_name() == api_parent; } @@ -43,8 +42,7 @@ const nyan::Object APIEffect::get_property(const nyan::Object &effect, 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()); + auto property_val = properties->get().at(property_type).get_value_ptr(); return db_view->get_object(property_val->get_name()); } diff --git a/libopenage/gamestate/api/patch.cpp b/libopenage/gamestate/api/patch.cpp index 8fb4544f2d..9d6baa787e 100644 --- a/libopenage/gamestate/api/patch.cpp +++ b/libopenage/gamestate/api/patch.cpp @@ -37,8 +37,7 @@ const nyan::Object APIPatch::get_property(const nyan::Object &patch, nyan::ValueHolder property_type = PATCH_PROPERTY_DEFS.get(property); std::shared_ptr db_view = patch.get_view(); - std::shared_ptr property_val = std::dynamic_pointer_cast( - properties->get().at(property_type).get_ptr()); + auto property_val = properties->get().at(property_type).get_value_ptr(); return db_view->get_object(property_val->get_name()); } diff --git a/libopenage/gamestate/api/player_setup.cpp b/libopenage/gamestate/api/player_setup.cpp index 3c685a9456..adef15c1cc 100644 --- a/libopenage/gamestate/api/player_setup.cpp +++ b/libopenage/gamestate/api/player_setup.cpp @@ -22,7 +22,7 @@ const std::vector APIPlayerSetup::get_modifiers(const nyan::Object auto db_view = player_setup.get_view(); auto modifiers = player_setup.get_set("PlayerSetup.modifiers"); for (auto &modifier_val : modifiers) { - auto modifier_obj_val = std::dynamic_pointer_cast(modifier_val.get_ptr()); + auto modifier_obj_val = modifier_val.get_value_ptr(); auto modifier_obj = db_view->get_object(modifier_obj_val->get_name()); result.push_back(modifier_obj); } @@ -36,7 +36,7 @@ const std::vector APIPlayerSetup::get_start_resources(const nyan:: auto db_view = player_setup.get_view(); auto start_resources = player_setup.get_set("PlayerSetup.starting_resources"); for (auto &resource_val : start_resources) { - auto resource_obj_val = std::dynamic_pointer_cast(resource_val.get_ptr()); + auto resource_obj_val = resource_val.get_value_ptr(); auto resource_obj = db_view->get_object(resource_obj_val->get_name()); result.push_back(resource_obj); } @@ -50,7 +50,7 @@ const std::vector APIPlayerSetup::get_patches(const nyan::Object & auto db_view = player_setup.get_view(); auto patches = player_setup.get_set("PlayerSetup.game_setup"); for (auto &patch_val : patches) { - auto patch_obj_val = std::dynamic_pointer_cast(patch_val.get_ptr()); + auto patch_obj_val = patch_val.get_value_ptr(); auto patch_obj = db_view->get_object(patch_obj_val->get_name()); result.push_back(patch_obj); } diff --git a/libopenage/gamestate/api/property.cpp b/libopenage/gamestate/api/property.cpp index e9af743b5b..6519514694 100644 --- a/libopenage/gamestate/api/property.cpp +++ b/libopenage/gamestate/api/property.cpp @@ -22,7 +22,7 @@ const std::vector APIAbilityProperty::get_animations(const nyan::O auto db_view = property.get_view(); auto animations = property.get_set("Animated.animations"); for (auto &anim_val : animations) { - auto anim_obj_val = std::dynamic_pointer_cast(anim_val.get_ptr()); + auto anim_obj_val = anim_val.get_value_ptr(); auto anim_obj = db_view->get_object(anim_obj_val->get_name()); result.push_back(anim_obj); } @@ -36,7 +36,7 @@ const std::vector APIAbilityProperty::get_command_sounds(const nya auto db_view = property.get_view(); auto command_sounds = property.get_set("CommandSound.sounds"); for (auto &sound_val : command_sounds) { - auto sound_obj_val = std::dynamic_pointer_cast(sound_val.get_ptr()); + auto sound_obj_val = sound_val.get_value_ptr(); auto sound_obj = db_view->get_object(sound_obj_val->get_name()); result.push_back(sound_obj); } @@ -50,7 +50,7 @@ const std::vector APIAbilityProperty::get_execution_sounds(const n auto db_view = property.get_view(); auto execution_sounds = property.get_set("ExecutionSound.sounds"); for (auto &sound_val : execution_sounds) { - auto sound_obj_val = std::dynamic_pointer_cast(sound_val.get_ptr()); + auto sound_obj_val = sound_val.get_value_ptr(); auto sound_obj = db_view->get_object(sound_obj_val->get_name()); result.push_back(sound_obj); } @@ -64,7 +64,7 @@ const std::vector APIAbilityProperty::get_diplo_stances(const nyan auto db_view = property.get_view(); auto diplo_stances = property.get_set("Diplomatic.stances"); for (auto &diplo_stance_val : diplo_stances) { - auto diplo_stance_obj_val = std::dynamic_pointer_cast(diplo_stance_val.get_ptr()); + auto diplo_stance_obj_val = diplo_stance_val.get_value_ptr(); auto diplo_stance_obj = db_view->get_object(diplo_stance_obj_val->get_name()); result.push_back(diplo_stance_obj); } diff --git a/libopenage/gamestate/api/resistance.cpp b/libopenage/gamestate/api/resistance.cpp index 8bce0dc29a..3e97b89f24 100644 --- a/libopenage/gamestate/api/resistance.cpp +++ b/libopenage/gamestate/api/resistance.cpp @@ -17,8 +17,7 @@ bool APIResistance::check_effect_type(const nyan::Object &resistance, nyan::fqon_t api_parent = get_api_parent(resistance); nyan::ValueHolder effect_type = RESISTANCE_DEFS.get(type); - std::shared_ptr effect_val = std::dynamic_pointer_cast( - effect_type.get_ptr()); + auto effect_val = effect_type.get_value_ptr(); return effect_val->get_name() == api_parent; } @@ -43,8 +42,7 @@ const nyan::Object APIResistance::get_property(const nyan::Object &resistance, 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()); + auto property_val = properties->get().at(property_type).get_value_ptr(); return db_view->get_object(property_val->get_name()); } diff --git a/libopenage/gamestate/api/terrain.cpp b/libopenage/gamestate/api/terrain.cpp index 5a787bf9f9..1c8e080132 100644 --- a/libopenage/gamestate/api/terrain.cpp +++ b/libopenage/gamestate/api/terrain.cpp @@ -25,8 +25,8 @@ const std::unordered_map APITerrain::get_path_costs(const nya nyan::dict_t path_costs = terrain.get_dict("Terrain.path_costs"); for (const auto &pair : path_costs) { - auto key = std::dynamic_pointer_cast(pair.first.get_ptr()); - auto value = std::dynamic_pointer_cast(pair.second.get_ptr()); + auto key = pair.first.get_value_ptr(); + auto value = pair.second.get_value_ptr(); result.emplace(key->get_name(), value->get()); } diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index dbde0d5193..2e9773cb96 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -173,7 +173,7 @@ void EntityFactory::init_components(const std::shared_ptr activity_ability; for (const auto &ability_val : abilities) { - auto ability_fqon = std::dynamic_pointer_cast(ability_val.get_ptr())->get_name(); + auto ability_fqon = ability_val.get_value_ptr()->get_name(); auto ability_obj = owner_db_view->get_object(ability_fqon); auto ability_parent = api::get_api_parent(ability_obj); @@ -200,7 +200,7 @@ void EntityFactory::init_components(const std::shared_ptr(setting.get_ptr()); + auto setting_obj_val = setting.get_value_ptr(); auto setting_obj = owner_db_view->get_object(setting_obj_val->get_name()); auto attribute = setting_obj.get_object("AttributeSetting.attribute"); auto start_value = setting_obj.get_int("AttributeSetting.starting_value"); @@ -367,7 +367,7 @@ void EntityFactory::init_activity(const std::shared_ptr(activity_node); auto conditions = nyan_node.get("XORGate.next"); for (auto &condition : conditions->get()) { - auto condition_value = std::dynamic_pointer_cast(condition.get_ptr()); + auto condition_value = condition.get_value_ptr(); auto condition_obj = owner_db_view->get_object_ptr(condition_value->get_name()); auto output_value = condition_obj->get("Condition.next")->get_name(); @@ -389,10 +389,10 @@ void EntityFactory::init_activity(const std::shared_ptr(activity_node); auto next = nyan_node.get("XOREventGate.next"); for (auto &next_node : next->get()) { - auto event_value = std::dynamic_pointer_cast(next_node.first.get_ptr()); + auto event_value = next_node.first.get_value_ptr(); auto event_obj = owner_db_view->get_object(event_value->get_name()); - auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); + auto next_node_value = next_node.second.get_value_ptr(); auto next_node_obj = owner_db_view->get_object(next_node_value->get_name()); auto output_id = visited[next_node_obj.get_name()]; diff --git a/libopenage/gamestate/system/apply_effect.cpp b/libopenage/gamestate/system/apply_effect.cpp index 0fb6831000..7c45f4b8b1 100644 --- a/libopenage/gamestate/system/apply_effect.cpp +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -98,12 +98,12 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptr> effects{}; for (auto &batch : batches) { - std::shared_ptr batch_obj_val = std::dynamic_pointer_cast(batch.get_ptr()); + auto batch_obj_val = batch.get_value_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_val = batch_effect.get_value_ptr(); auto effect_obj = effect_ability.get_view()->get_object(effect_obj_val->get_name()); auto effect_type = api::APIEffect::get_type(effect_obj); @@ -118,7 +118,7 @@ const time::time_t ApplyEffect::apply_effect(const std::shared_ptr> resistances{}; for (auto &resistance : resistances_set) { - std::shared_ptr resistance_obj_val = std::dynamic_pointer_cast(resistance.get_ptr()); + auto resistance_obj_val = resistance.get_value_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); From a8df5d58d10575a9d133ec97dbe158e107209dd3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Aug 2025 18:00:25 +0200 Subject: [PATCH 117/118] gamestate: Rename condition_t to condition_function_t. --- libopenage/gamestate/activity/tests.cpp | 16 ++++++++-------- libopenage/gamestate/activity/types.h | 10 +++++----- libopenage/gamestate/api/activity.cpp | 2 +- libopenage/gamestate/api/activity.h | 2 +- libopenage/gamestate/api/definitions.h | 2 +- libopenage/gamestate/entity_factory.cpp | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/libopenage/gamestate/activity/tests.cpp b/libopenage/gamestate/activity/tests.cpp index ee9b193b6c..e270edd70b 100644 --- a/libopenage/gamestate/activity/tests.cpp +++ b/libopenage/gamestate/activity/tests.cpp @@ -233,10 +233,10 @@ void activity_demo() { // Conditional branch static size_t counter = 0; - activity::condition_t branch_task1 = [&](const time::time_t & /* time */, - const std::shared_ptr & /* entity */, - const std::shared_ptr & /* state */, - const std::shared_ptr & /* api_object */) { + activity::condition_function_t branch_task1 = [&](const time::time_t & /* time */, + const std::shared_ptr & /* entity */, + const std::shared_ptr & /* state */, + const std::shared_ptr & /* api_object */) { log::log(INFO << "Checking condition (counter < 4): counter=" << counter); if (counter < 4) { log::log(INFO << "Selecting path 1 (back to task node " << task1->get_id() << ")"); @@ -249,10 +249,10 @@ void activity_demo() { xor_node->add_output(task1, {nullptr, // API object set to nullptr as it's never used by condition func branch_task1}); - activity::condition_t branch_event = [&](const time::time_t & /* time */, - const std::shared_ptr & /* entity */, - const std::shared_ptr & /* state */, - const std::shared_ptr & /* api_object */) { + activity::condition_function_t branch_event = [&](const time::time_t & /* time */, + const std::shared_ptr & /* entity */, + const std::shared_ptr & /* state */, + const std::shared_ptr & /* api_object */) { // No check needed here, the event node is always selected log::log(INFO << "Selecting path 2 (to event node " << event_node->get_id() << ")"); return true; diff --git a/libopenage/gamestate/activity/types.h b/libopenage/gamestate/activity/types.h index 41f24dc857..b0320dbd1b 100644 --- a/libopenage/gamestate/activity/types.h +++ b/libopenage/gamestate/activity/types.h @@ -68,10 +68,10 @@ using event_primer_t = std::function(cons * * @return true if the output node is chosen, false otherwise. */ -using condition_t = std::function &entity, - const std::shared_ptr &state, - const std::shared_ptr &api_object)>; +using condition_function_t = std::function &entity, + const std::shared_ptr &state, + const std::shared_ptr &api_object)>; /** * Condition used to determine if an output node is chosen. @@ -81,7 +81,7 @@ struct condition { std::shared_ptr api_object; /// Checks whether the condition is true. /// TODO: We could look this function up at runtime. - condition_t function; + condition_function_t function; }; /** diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp index a4f7b6407d..b72b2630a6 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -151,7 +151,7 @@ bool APIActivityCondition::is_condition(const nyan::Object &obj) { return api_parent == "engine.util.activity.condition.Condition"; } -activity::condition_t APIActivityCondition::get_condition(const nyan::Object &condition) { +activity::condition_function_t APIActivityCondition::get_condition(const nyan::Object &condition) { nyan::fqon_t api_parent = get_api_parent(condition); return ACTIVITY_CONDITION_LOOKUP.get(api_parent); diff --git a/libopenage/gamestate/api/activity.h b/libopenage/gamestate/api/activity.h index f0e1f93475..0b98e62157 100644 --- a/libopenage/gamestate/api/activity.h +++ b/libopenage/gamestate/api/activity.h @@ -107,7 +107,7 @@ class APIActivityCondition { * * @return Condition function. */ - static activity::condition_t get_condition(const nyan::Object &condition); + static activity::condition_function_t get_condition(const nyan::Object &condition); }; /** diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index 0566ab8e7a..a67d663e0a 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -311,7 +311,7 @@ static const auto ACTIVITY_TASK_SYSTEM_LOOKUP = datastructure::create_const_map< /** * Maps API activity condition types to engine condition types. */ -static const auto ACTIVITY_CONDITION_LOOKUP = datastructure::create_const_map( +static const auto ACTIVITY_CONDITION_LOOKUP = 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.NextCommand", diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index 2e9773cb96..e0e7e8abee 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -80,10 +80,10 @@ std::shared_ptr create_test_activity() { idle->set_system_id(system::system_id_t::IDLE); // branch 1: check if the entity is moveable - activity::condition_t command_branch = [&](const time::time_t & /* time */, - const std::shared_ptr &entity, - const std::shared_ptr & /* state */, - const std::shared_ptr & /* api_object */) { + activity::condition_function_t command_branch = [&](const time::time_t & /* time */, + const std::shared_ptr &entity, + const std::shared_ptr & /* state */, + const std::shared_ptr & /* api_object */) { return entity->has_component(component::component_t::MOVE); }; condition_moveable->add_output(condition_command, From 0c74ab96d91ea78d41b4496be1aa4e4600dee045 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 18 Aug 2025 02:34:29 +0200 Subject: [PATCH 118/118] gamestate: Replace optional+variant type with std::monostate. --- .../activity/condition/target_in_range.cpp | 8 ++++---- .../component/internal/command_queue.cpp | 4 ++-- .../gamestate/component/internal/command_queue.h | 4 ++-- libopenage/gamestate/system/move.cpp | 15 +++++---------- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/libopenage/gamestate/activity/condition/target_in_range.cpp b/libopenage/gamestate/activity/condition/target_in_range.cpp index ca95650084..b2536d9de3 100644 --- a/libopenage/gamestate/activity/condition/target_in_range.cpp +++ b/libopenage/gamestate/activity/condition/target_in_range.cpp @@ -27,7 +27,7 @@ bool target_in_range(const time::time_t &time, entity->get_component(component::component_t::COMMANDQUEUE)); auto target = command_queue->get_target(time); - if (not target.has_value()) { + if (std::holds_alternative(target)) { // No target exists, exit early log::log(DBG << "Target for entity " << entity->get_id() << " is not set"); return false; @@ -69,11 +69,11 @@ bool target_in_range(const time::time_t &time, entity->get_component(component::component_t::POSITION)); auto current_pos = position->get_positions().get(time); - if (std::holds_alternative(target.value())) { + if (std::holds_alternative(target)) { // Target is a position log::log(DBG << "Target is a position"); - auto target_pos = std::get(target.value()); + auto target_pos = std::get(target); log::log(DBG << "Target position: " << target_pos); auto distance = (target_pos - current_pos).length(); @@ -83,7 +83,7 @@ bool target_in_range(const time::time_t &time, } // Target is a game entity - auto target_entity_id = std::get(target.value()); + auto target_entity_id = std::get(target); log::log(DBG << "Target is a game entity with ID " << target_entity_id); if (not state->has_game_entity(target_entity_id)) { // Target entity does not exist diff --git a/libopenage/gamestate/component/internal/command_queue.cpp b/libopenage/gamestate/component/internal/command_queue.cpp index d05e2ea216..8ecce5f251 100644 --- a/libopenage/gamestate/component/internal/command_queue.cpp +++ b/libopenage/gamestate/component/internal/command_queue.cpp @@ -56,7 +56,7 @@ const std::shared_ptr CommandQueue::front(const time::time_t & CommandQueue::optional_target_t CommandQueue::get_target(const time::time_t &time) const { if (this->command_queue.empty(time)) { - return std::nullopt; + return std::monostate{}; } auto command = this->command_queue.front(time); @@ -68,7 +68,7 @@ CommandQueue::optional_target_t CommandQueue::get_target(const time::time_t &tim case command::command_t::APPLY_EFFECT: return std::dynamic_pointer_cast(command)->get_target(); default: - return std::nullopt; + return std::monostate{}; } } diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index 45a9ec39d1..9c7b5c5e15 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -94,11 +94,11 @@ class CommandQueue final : public InternalComponent { * Target type with several possible representations. * * Can be: + * - std::monostate: No target. * - coord::phys3: Position in the game world. * - entity_id_t: ID of another entity. - * - std::nullopt: Nothing. */ - using optional_target_t = std::optional>; + using optional_target_t = std::variant; /** * Get the target of the entity at the given time. diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index f69035a931..6fb1f5c54e 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -105,11 +105,6 @@ const time::time_t Move::move_target(const std::shared_ptrget_component(component::component_t::COMMANDQUEUE)); auto target = command_queue->get_target(start_time); - if (not target.has_value()) [[unlikely]] { - log::log(WARN << "Entity " << entity->get_id() << " has no target at time " << start_time); - return time::time_t::from_int(0); - } - return std::visit( overloaded{ [&](const gamestate::entity_id_t &target_id) { @@ -125,11 +120,11 @@ const time::time_t Move::move_target(const std::shared_ptrget_id() << " has an invalid target type: " - << typeid(target).name() << " at time " << start_time); - return time::time_t::from_int(0); }}, - target.value()); + [&](const std::monostate &) { + log::log(WARN << "Entity " << entity->get_id() << " has no target at time " << start_time); + return time::time_t::from_int(0); + }}, + target); }