From ce46a920d5c90733b61a9a95e82c9596627d0d54 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:16:59 +0200 Subject: [PATCH 001/163] 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 3e6ec245e1cf1bbaad982aa8ce69c84798899d81 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:17:34 +0200 Subject: [PATCH 002/163] 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 0481db882a8c30bab4e5ff7051d04b587d49fc73 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:46:56 +0200 Subject: [PATCH 003/163] 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 bde650c415995238ffb9531ce353ff042f281ce9 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 2 Sep 2024 00:49:41 +0200 Subject: [PATCH 004/163] 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 9cc02960c45b8448df9f31206dfcacb136f96482 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:04:54 +0200 Subject: [PATCH 005/163] 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 7090f3bb79f009cc6f75ff4854b38465f3d4c792 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:06:04 +0200 Subject: [PATCH 006/163] 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 39a85568e6e6dd518b27fa8bb633d9e1683c07c5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:12:37 +0200 Subject: [PATCH 007/163] 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 b93aa12cca63581c2db68da6647bc28f4e3c7cb3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 03:45:30 +0200 Subject: [PATCH 008/163] 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 055018cf2ddf0a5b745aef0a94109b3721c02118 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 7 Sep 2024 04:29:14 +0200 Subject: [PATCH 009/163] 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 e7e9a58513a0a2a8eab4fd9ae8fee83c8bfd2d49 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 01:11:03 +0200 Subject: [PATCH 010/163] 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 949b66ce210eaf58d922335b0fda153c30a2de9f Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 01:15:08 +0200 Subject: [PATCH 011/163] 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 8a2b815ac328b19bdf220280d95532d315fa1e88 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 01:19:58 +0200 Subject: [PATCH 012/163] 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 96624e81e1b4e11fff87c6d5f2b619aba61df691 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 17:29:25 +0200 Subject: [PATCH 013/163] 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 f72201661e356ea68d60b37b91cfa9c2037dc21e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Sep 2024 21:57:15 +0200 Subject: [PATCH 014/163] 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 55248fdf68c9ceaf41db95683e18312dc8cdfc21 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 13 Sep 2024 23:48:54 +0200 Subject: [PATCH 015/163] 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 f9f517eb7f2e5909a73085416a39aa5acc3c1faa Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 14 Sep 2024 13:49:26 +0200 Subject: [PATCH 016/163] 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 1071ac0e8eef69db2b05fdc3fff15601f5129bf0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 14 Sep 2024 13:51:26 +0200 Subject: [PATCH 017/163] 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 b447094b4869a523be7a238faa76f7c80d94c751 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 06:18:08 +0200 Subject: [PATCH 018/163] 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 b19942ff83613413339348c699ebc1d2401491ce Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:25:58 +0200 Subject: [PATCH 019/163] 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 003b8643f2311dbb97a4968aaff26a6e4cbbc839 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:27:03 +0200 Subject: [PATCH 020/163] 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 c6eff84d26fa75620428853b03db13a55c314f3e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:38:11 +0200 Subject: [PATCH 021/163] 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 7c3df4a3911533d5b16adc167e943a062b1a9b01 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:52:51 +0200 Subject: [PATCH 022/163] 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 f4e37cce3f22bef3099c8c0813139d2ef51ac318 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 15 Sep 2024 12:53:09 +0200 Subject: [PATCH 023/163] 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 185efff81e6919e34bbe98510630df915f3f5001 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 16 Oct 2024 07:39:09 +0200 Subject: [PATCH 024/163] 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 74d70c354002699ab2247d60c73935c1999d0106 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 16 Oct 2024 07:45:57 +0200 Subject: [PATCH 025/163] 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 437e4b596b0bef28a870820720f9e05b4ab3dbca Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 16 Oct 2024 22:11:40 +0200 Subject: [PATCH 026/163] 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 b2921050234a2a1758bf759ded9f019ccab695f7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 18 Oct 2024 06:39:45 +0200 Subject: [PATCH 027/163] 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 63ababf21a845e2ad302cebbf0a2a9f8c439d316 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 18 Oct 2024 23:11:59 +0200 Subject: [PATCH 028/163] 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 ddb84834fe69d6e869bb8a52d9f489b611379e4a Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 18 Oct 2024 23:12:17 +0200 Subject: [PATCH 029/163] 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 163b40ed2318810064ff475853770c3a84924977 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 19 Oct 2024 22:04:39 +0200 Subject: [PATCH 030/163] 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 5d034028d0663922824929d64db0de3e03042e35 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 19 Oct 2024 22:09:14 +0200 Subject: [PATCH 031/163] 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 7992af4bc3601c5596231e3364ceff509dbba7c1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 00:23:26 +0200 Subject: [PATCH 032/163] 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 cbb3962163cc97d55f1f5354707ee7a6a08fe49d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 15:04:23 +0200 Subject: [PATCH 033/163] 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 8c98932c2229d987bc552f10f1ca7a9d4abb018b Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 17:12:10 +0200 Subject: [PATCH 034/163] 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 b1d242ae389433d18ceb213b64fc6db4f475deae Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Oct 2024 19:51:42 +0200 Subject: [PATCH 035/163] 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 59eaaec5fce8ffee6a6ae2bd3d192bc0a4f97614 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 22 Oct 2024 06:06:49 +0200 Subject: [PATCH 036/163] 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 e3bde387c2be095d63b2de6dea2a764761e0b194 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 4 Nov 2024 02:00:19 +0100 Subject: [PATCH 037/163] 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 85676b25c658d59dce7166455f35abb490f5acef Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 04:23:10 +0100 Subject: [PATCH 038/163] 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 76400846c36ea2c91ef804cc810b30a97f4d6711 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 05:11:38 +0100 Subject: [PATCH 039/163] 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 1a4f4e4c6264c43a9e3efb2504130ccfd10e8476 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 05:32:48 +0100 Subject: [PATCH 040/163] 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 3717f67c0a4dd36c2abec0e5550fb6b10dfd98a0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 5 Nov 2024 23:57:37 +0100 Subject: [PATCH 041/163] 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 3f5ec2e8ee714e69363938ede4e1880fc2f46bb5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 7 Nov 2024 22:34:19 +0100 Subject: [PATCH 042/163] 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 f066157492fa4cbda69464d92e81714f0044a14c Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 8 Apr 2025 00:07:12 +0200 Subject: [PATCH 043/163] 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 0ef81d0b11e584fc0b58163afbddb2854cc9e944 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 8 Apr 2025 00:54:21 +0200 Subject: [PATCH 044/163] 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 f9925f5e245aa2f863865ec2d4ade8ae4bc41892 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 01:30:04 +0200 Subject: [PATCH 045/163] 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/163] 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 ea4ca84a02d6942d76db0775a878aa502c54b802 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 02:02:26 +0200 Subject: [PATCH 047/163] 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 94a42c1a55ab70d132b18d1df3ea49522b63d0e0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 02:03:20 +0200 Subject: [PATCH 048/163] 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 a6fcaa537112eb14be64d2aea066f21a32a01468 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 04:55:09 +0200 Subject: [PATCH 049/163] 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 8b8f6182fec449bb012a69d04c016135c929503b Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 10 Apr 2025 04:55:54 +0200 Subject: [PATCH 050/163] 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 1a30920d97c5ef9802ea2f72ae40d97626ad08fa Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 11 Apr 2025 20:58:34 +0200 Subject: [PATCH 051/163] 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 ac2c1de09ae08ae59c3576385652bd64f649367f Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 11 Apr 2025 21:59:12 +0200 Subject: [PATCH 052/163] 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 1a1e3c2844febd17328786a74315599b093bc3ab Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Apr 2025 02:00:01 +0200 Subject: [PATCH 053/163] 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 e783063d3069e92f8fa72274b8d46e0715496dd4 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Apr 2025 02:30:00 +0200 Subject: [PATCH 054/163] 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 596f889f4b1f1beb91a8985c565a87a59acf4342 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Apr 2025 03:28:52 +0200 Subject: [PATCH 055/163] 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 8947cea7474e3aa95e53f12971ae20f46ea57b30 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 18:42:47 +0200 Subject: [PATCH 056/163] 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 78281ea7acfec06265ce3e8878c43a9e6ad0e888 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 18:43:47 +0200 Subject: [PATCH 057/163] 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 49f240ef4a1b83d11ae56ff35cec741465d47a03 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 18:45:26 +0200 Subject: [PATCH 058/163] 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 8d9add3273ad213593770d598cf7fdd2a0725f24 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 19:03:38 +0200 Subject: [PATCH 059/163] 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 489b6ea92e3d199bb7e8e945e52898d41b1a6d26 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 22:01:55 +0200 Subject: [PATCH 060/163] 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 d15844d7e3da2ea0f2a042e3d4342ebdb6746768 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 22:45:30 +0200 Subject: [PATCH 061/163] 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 ff26d2915d57a8c5e9f13897bc6805cde8eafbf6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 23:29:20 +0200 Subject: [PATCH 062/163] 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 a0f66a8e7f1a05f272f4b54b999c8c02477aa871 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 23:33:06 +0200 Subject: [PATCH 063/163] 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 df806706a6a23e9ba01fb3266faf223f60352030 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 21 Apr 2025 23:37:22 +0200 Subject: [PATCH 064/163] 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 ce714ee0023879491cf1887c6704e0a3d68e1b36 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 22 Apr 2025 00:08:01 +0200 Subject: [PATCH 065/163] 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 70d5aa3f67e6f3da49b2e5ef9dc66f57e64d8c0d Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 22 Apr 2025 23:45:19 +0200 Subject: [PATCH 066/163] 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 01f27e83f98a03e026b9f13678f34ccb841eb747 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 4 May 2025 16:58:56 +0200 Subject: [PATCH 067/163] 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 e4c8b2007b669fe8d1f60ffb64f03f7e4f666743 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 4 May 2025 17:14:52 +0200 Subject: [PATCH 068/163] 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 8918e2682e4a7e1fdfb508e984fc51335210a21a Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 16:36:31 +0200 Subject: [PATCH 069/163] 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 6c99a9f497e00f0a3272150b2bf7cba35484b66f Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 17:12:48 +0200 Subject: [PATCH 070/163] 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 24da167c5c787f7c7071b4859b55bbdbe2b66d8d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 21:42:58 +0200 Subject: [PATCH 071/163] 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 f361a80244c4528f151a953e3ab33ed3e2d5e3ab Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 22:51:20 +0200 Subject: [PATCH 072/163] 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 8c696c577b27831608578500e84f403beabdbd07 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 10 May 2025 23:03:34 +0200 Subject: [PATCH 073/163] 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 3849106e322965cbc5b174e4436bff8320ce14ec Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 02:06:57 +0200 Subject: [PATCH 074/163] 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 7600100a0d544eb938ff51ae2354ce4ddcc53faf Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 02:14:39 +0200 Subject: [PATCH 075/163] 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 c762aaa3998a5b0832cc2c2aae9faac9eb909e04 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 02:35:52 +0200 Subject: [PATCH 076/163] 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 5d079d3808bee917f1089107f3d4febf501b9b5d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 02:46:42 +0200 Subject: [PATCH 077/163] 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 8d94455c1ed9adceb09eaf985837c4cf68ef7ec4 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 03:04:15 +0200 Subject: [PATCH 078/163] 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 bda870724a09147c7d9e5d2571ed7710b6b8b9e4 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 04:35:23 +0200 Subject: [PATCH 079/163] 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 ca4c189b32be0ac97a1e45e560cd8417bf23d424 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 04:41:08 +0200 Subject: [PATCH 080/163] 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 02641d5111d1c3809162c4612b4d54dded31528c Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 04:51:28 +0200 Subject: [PATCH 081/163] 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 f305e0959a212ea415ae196226f21233b5d0a8bf Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 04:52:06 +0200 Subject: [PATCH 082/163] 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 3d6b0ff83ff79d94ee4545916b1428ad1f63f7c8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 18:04:57 +0200 Subject: [PATCH 083/163] 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 6cd31eec8ba15cfea2801c3c0b0a55bee2c54dfb Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 21:06:06 +0200 Subject: [PATCH 084/163] 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 6a75d3970fff42cf1024e13ad165ba8f7ff17b29 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 21:28:43 +0200 Subject: [PATCH 085/163] 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 f572563752823adc284f692dac10aeaf4e508cec Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 22:29:25 +0200 Subject: [PATCH 086/163] 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 23b0e6b34ca0556b5eb6020e803e947a6dba8269 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 22:29:53 +0200 Subject: [PATCH 087/163] 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 9ec6236feeab96f1efb3517fa148d622ee7016ba Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 22:30:48 +0200 Subject: [PATCH 088/163] 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 bb5cc6811d2915801b8bdd6ab608a46165fc5be8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 11 May 2025 22:31:15 +0200 Subject: [PATCH 089/163] 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/163] 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 648061681be4adbd9b90ca53ad553eb254204789 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 12 May 2025 00:01:57 +0200 Subject: [PATCH 091/163] 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 a5f42a20e24fd7ed67dad6f5c25c6894fcfb0478 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 12 May 2025 00:08:49 +0200 Subject: [PATCH 092/163] 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 2c0eefedd62594c70aeb0fd55fe9cbd5d0cb29b3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 12 May 2025 00:56:13 +0200 Subject: [PATCH 093/163] 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 6d1cb271b4f68bc0d67fd546ba0c8328aeffd55c Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 12 May 2025 00:59:03 +0200 Subject: [PATCH 094/163] 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 a5fd975e52a1d6c1d1356cc45266a386aec82511 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 01:46:22 +0200 Subject: [PATCH 095/163] 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 91e9cb61cc787d01f4b80165385bd3f7788c3b18 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 02:41:36 +0200 Subject: [PATCH 096/163] 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 b2a177ac31e553852348b2c669c3203c80d3bbf5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 02:55:17 +0200 Subject: [PATCH 097/163] 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 15619e9f857e6539757e1681ea85270327122a8d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 02:59:10 +0200 Subject: [PATCH 098/163] 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 ade21bb0e28ee75084ad59700da94b932fb01605 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 03:44:12 +0200 Subject: [PATCH 099/163] 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 19c11e29f5f392e65e077a3bbf819b80eb4c2baa Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 03:45:59 +0200 Subject: [PATCH 100/163] 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 7219ee86553c197936f3e49e8b47833e1ebd14e6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 03:54:39 +0200 Subject: [PATCH 101/163] 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 1514e7c77c1cdec2cbe431f73724fcfc0e94c037 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 04:25:37 +0200 Subject: [PATCH 102/163] 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 d0684a6f33ee8862c0882d5d6ea2dccedad1696e Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 14:09:27 +0200 Subject: [PATCH 103/163] 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 787b8d148e9c4c2a700dc04a1984f98e6392793a Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 14:29:20 +0200 Subject: [PATCH 104/163] 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/163] 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/163] 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 39ea80601054d93b7d3772fb8d0357817afcaabb Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 17:00:07 +0200 Subject: [PATCH 107/163] 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 496a644b30f89dabbe6633260d5d06b00fedc9a9 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 21:41:47 +0200 Subject: [PATCH 108/163] 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 4cb8cdb60ded2ee4e6df63e8e0e5383af867f8ac Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 18 May 2025 21:59:22 +0200 Subject: [PATCH 109/163] 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 456d3d6ee346249116ee579d1818168d159218e0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 28 May 2025 07:15:18 +0200 Subject: [PATCH 110/163] convert: Move AoCSubprocessor methods into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 4 + .../conversion/aoc/ability/CMakeLists.txt | 58 + .../conversion/aoc/ability/__init__.py | 6 + .../aoc/ability/active_transform_to.py | 24 + .../conversion/aoc/ability/activity.py | 55 + .../aoc/ability/apply_continuous_effect.py | 277 + .../aoc/ability/apply_discrete_effect.py | 341 + .../aoc/ability/attribute_change_tracker.py | 134 + .../conversion/aoc/ability/collect_storage.py | 65 + .../conversion/aoc/ability/collision.py | 74 + .../conversion/aoc/ability/constructable.py | 1303 +++ .../conversion/aoc/ability/create.py | 89 + .../processor/conversion/aoc/ability/death.py | 296 + .../conversion/aoc/ability/delete.py | 126 + .../conversion/aoc/ability/despawn.py | 167 + .../conversion/aoc/ability/drop_resources.py | 125 + .../conversion/aoc/ability/drop_site.py | 92 + .../conversion/aoc/ability/enter_container.py | 83 + .../aoc/ability/exchange_resources.py | 82 + .../conversion/aoc/ability/exit_container.py | 70 + .../conversion/aoc/ability/formation.py | 98 + .../conversion/aoc/ability/foundation.py | 59 + .../aoc/ability/game_entity_stance.py | 102 + .../conversion/aoc/ability/gather.py | 256 + .../conversion/aoc/ability/harvestable.py | 397 + .../processor/conversion/aoc/ability/herd.py | 94 + .../conversion/aoc/ability/herdable.py | 55 + .../processor/conversion/aoc/ability/idle.py | 121 + .../conversion/aoc/ability/line_of_sight.py | 74 + .../processor/conversion/aoc/ability/live.py | 122 + .../processor/conversion/aoc/ability/move.py | 312 + .../processor/conversion/aoc/ability/named.py | 108 + .../conversion/aoc/ability/overlay_terrain.py | 56 + .../conversion/aoc/ability/pathable.py | 62 + .../aoc/ability/production_queue.py | 75 + .../conversion/aoc/ability/projectile.py | 128 + .../aoc/ability/provide_contingent.py | 97 + .../conversion/aoc/ability/rally_point.py | 44 + .../aoc/ability/regenerate_attribute.py | 96 + .../aoc/ability/regenerate_resource_spot.py | 24 + .../conversion/aoc/ability/remove_storage.py | 65 + .../conversion/aoc/ability/research.py | 91 + .../conversion/aoc/ability/resistance.py | 67 + .../aoc/ability/resource_storage.py | 269 + .../conversion/aoc/ability/restock.py | 155 + .../conversion/aoc/ability/selectable.py | 238 + .../aoc/ability/send_back_to_task.py | 56 + .../aoc/ability/shoot_projectile.py | 290 + .../processor/conversion/aoc/ability/stop.py | 69 + .../conversion/aoc/ability/storage.py | 454 + .../aoc/ability/terrain_requirement.py | 68 + .../processor/conversion/aoc/ability/trade.py | 79 + .../conversion/aoc/ability/trade_post.py | 82 + .../aoc/ability/transfer_storage.py | 94 + .../processor/conversion/aoc/ability/turn.py | 94 + .../conversion/aoc/ability/use_contingent.py | 91 + .../processor/conversion/aoc/ability/util.py | 302 + .../conversion/aoc/ability/visibility.py | 135 + .../conversion/aoc/ability_subprocessor.py | 7753 +---------------- .../conversion/aoc/effect/CMakeLists.txt | 8 + .../conversion/aoc/effect/__init__.py | 5 + .../processor/conversion/aoc/effect/attack.py | 118 + .../conversion/aoc/effect/construct.py | 107 + .../conversion/aoc/effect/convert.py | 136 + .../processor/conversion/aoc/effect/heal.py | 111 + .../processor/conversion/aoc/effect/repair.py | 140 + .../conversion/aoc/effect_subprocessor.py | 991 +-- .../conversion/aoc/resistance/CMakeLists.txt | 8 + .../conversion/aoc/resistance/__init__.py | 5 + .../conversion/aoc/resistance/attack.py | 100 + .../conversion/aoc/resistance/construct.py | 110 + .../conversion/aoc/resistance/convert.py | 93 + .../conversion/aoc/resistance/heal.py | 79 + .../conversion/aoc/resistance/repair.py | 98 + 74 files changed, 9809 insertions(+), 8603 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/ability/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/ability/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/ability/active_transform_to.py create mode 100644 openage/convert/processor/conversion/aoc/ability/activity.py create mode 100644 openage/convert/processor/conversion/aoc/ability/apply_continuous_effect.py create mode 100644 openage/convert/processor/conversion/aoc/ability/apply_discrete_effect.py create mode 100644 openage/convert/processor/conversion/aoc/ability/attribute_change_tracker.py create mode 100644 openage/convert/processor/conversion/aoc/ability/collect_storage.py create mode 100644 openage/convert/processor/conversion/aoc/ability/collision.py create mode 100644 openage/convert/processor/conversion/aoc/ability/constructable.py create mode 100644 openage/convert/processor/conversion/aoc/ability/create.py create mode 100644 openage/convert/processor/conversion/aoc/ability/death.py create mode 100644 openage/convert/processor/conversion/aoc/ability/delete.py create mode 100644 openage/convert/processor/conversion/aoc/ability/despawn.py create mode 100644 openage/convert/processor/conversion/aoc/ability/drop_resources.py create mode 100644 openage/convert/processor/conversion/aoc/ability/drop_site.py create mode 100644 openage/convert/processor/conversion/aoc/ability/enter_container.py create mode 100644 openage/convert/processor/conversion/aoc/ability/exchange_resources.py create mode 100644 openage/convert/processor/conversion/aoc/ability/exit_container.py create mode 100644 openage/convert/processor/conversion/aoc/ability/formation.py create mode 100644 openage/convert/processor/conversion/aoc/ability/foundation.py create mode 100644 openage/convert/processor/conversion/aoc/ability/game_entity_stance.py create mode 100644 openage/convert/processor/conversion/aoc/ability/gather.py create mode 100644 openage/convert/processor/conversion/aoc/ability/harvestable.py create mode 100644 openage/convert/processor/conversion/aoc/ability/herd.py create mode 100644 openage/convert/processor/conversion/aoc/ability/herdable.py create mode 100644 openage/convert/processor/conversion/aoc/ability/idle.py create mode 100644 openage/convert/processor/conversion/aoc/ability/line_of_sight.py create mode 100644 openage/convert/processor/conversion/aoc/ability/live.py create mode 100644 openage/convert/processor/conversion/aoc/ability/move.py create mode 100644 openage/convert/processor/conversion/aoc/ability/named.py create mode 100644 openage/convert/processor/conversion/aoc/ability/overlay_terrain.py create mode 100644 openage/convert/processor/conversion/aoc/ability/pathable.py create mode 100644 openage/convert/processor/conversion/aoc/ability/production_queue.py create mode 100644 openage/convert/processor/conversion/aoc/ability/projectile.py create mode 100644 openage/convert/processor/conversion/aoc/ability/provide_contingent.py create mode 100644 openage/convert/processor/conversion/aoc/ability/rally_point.py create mode 100644 openage/convert/processor/conversion/aoc/ability/regenerate_attribute.py create mode 100644 openage/convert/processor/conversion/aoc/ability/regenerate_resource_spot.py create mode 100644 openage/convert/processor/conversion/aoc/ability/remove_storage.py create mode 100644 openage/convert/processor/conversion/aoc/ability/research.py create mode 100644 openage/convert/processor/conversion/aoc/ability/resistance.py create mode 100644 openage/convert/processor/conversion/aoc/ability/resource_storage.py create mode 100644 openage/convert/processor/conversion/aoc/ability/restock.py create mode 100644 openage/convert/processor/conversion/aoc/ability/selectable.py create mode 100644 openage/convert/processor/conversion/aoc/ability/send_back_to_task.py create mode 100644 openage/convert/processor/conversion/aoc/ability/shoot_projectile.py create mode 100644 openage/convert/processor/conversion/aoc/ability/stop.py create mode 100644 openage/convert/processor/conversion/aoc/ability/storage.py create mode 100644 openage/convert/processor/conversion/aoc/ability/terrain_requirement.py create mode 100644 openage/convert/processor/conversion/aoc/ability/trade.py create mode 100644 openage/convert/processor/conversion/aoc/ability/trade_post.py create mode 100644 openage/convert/processor/conversion/aoc/ability/transfer_storage.py create mode 100644 openage/convert/processor/conversion/aoc/ability/turn.py create mode 100644 openage/convert/processor/conversion/aoc/ability/use_contingent.py create mode 100644 openage/convert/processor/conversion/aoc/ability/util.py create mode 100644 openage/convert/processor/conversion/aoc/ability/visibility.py create mode 100644 openage/convert/processor/conversion/aoc/effect/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/effect/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/effect/attack.py create mode 100644 openage/convert/processor/conversion/aoc/effect/construct.py create mode 100644 openage/convert/processor/conversion/aoc/effect/convert.py create mode 100644 openage/convert/processor/conversion/aoc/effect/heal.py create mode 100644 openage/convert/processor/conversion/aoc/effect/repair.py create mode 100644 openage/convert/processor/conversion/aoc/resistance/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/resistance/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/resistance/attack.py create mode 100644 openage/convert/processor/conversion/aoc/resistance/construct.py create mode 100644 openage/convert/processor/conversion/aoc/resistance/convert.py create mode 100644 openage/convert/processor/conversion/aoc/resistance/heal.py create mode 100644 openage/convert/processor/conversion/aoc/resistance/repair.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index 00d44e8d3a..c65ce6ef57 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -16,3 +16,7 @@ add_py_modules( upgrade_effect_subprocessor.py upgrade_resource_subprocessor.py ) + +add_subdirectory(ability) +add_subdirectory(effect) +add_subdirectory(resistance) diff --git a/openage/convert/processor/conversion/aoc/ability/CMakeLists.txt b/openage/convert/processor/conversion/aoc/ability/CMakeLists.txt new file mode 100644 index 0000000000..b4c9354461 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/CMakeLists.txt @@ -0,0 +1,58 @@ +add_py_modules( + __init__.py + active_transform_to.py + activity.py + apply_continuous_effect.py + apply_discrete_effect.py + attribute_change_tracker.py + collect_storage.py + collision.py + constructable.py + create.py + death.py + delete.py + despawn.py + drop_resources.py + drop_site.py + enter_container.py + exchange_resources.py + exit_container.py + formation.py + foundation.py + game_entity_stance.py + gather.py + harvestable.py + herdable.py + herd.py + idle.py + line_of_sight.py + live.py + move.py + named.py + overlay_terrain.py + pathable.py + production_queue.py + projectile.py + provide_contingent.py + rally_point.py + regenerate_attribute.py + regenerate_resource_spot.py + remove_storage.py + research.py + resistance.py + resource_storage.py + restock.py + selectable.py + send_back_to_task.py + shoot_projectile.py + stop.py + storage.py + terrain_requirement.py + trade_post.py + trade.py + transfer_storage.py + turn.py + use_contingent.py + util.py + visibility.py +) diff --git a/openage/convert/processor/conversion/aoc/ability/__init__.py b/openage/convert/processor/conversion/aoc/ability/__init__.py new file mode 100644 index 0000000000..93261ab007 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Derives and adds abilities to game entities created from lines. Subroutine of the +nyan subprocessor. +""" diff --git a/openage/convert/processor/conversion/aoc/ability/active_transform_to.py b/openage/convert/processor/conversion/aoc/ability/active_transform_to.py new file mode 100644 index 0000000000..6236b54106 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/active_transform_to.py @@ -0,0 +1,24 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ActiveTransformTo ability. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def active_transform_to_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ActiveTransformTo 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 + """ + # TODO: Implement + return None diff --git a/openage/convert/processor/conversion/aoc/ability/activity.py b/openage/convert/processor/conversion/aoc/ability/activity.py new file mode 100644 index 0000000000..e08509308b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/activity.py @@ -0,0 +1,55 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Activity ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +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 diff --git a/openage/convert/processor/conversion/aoc/ability/apply_continuous_effect.py b/openage/convert/processor/conversion/aoc/ability/apply_continuous_effect.py new file mode 100644 index 0000000000..ae8d617ff7 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/apply_continuous_effect.py @@ -0,0 +1,277 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ApplyContinuousEffect ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation, create_civ_animation, create_sound +from ..effect_subprocessor import AoCEffectSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def apply_continuous_effect_ability( + line: GenieGameEntityGroup, + command_id: int, + ranged: bool = False +) -> ForwardRef: + """ + Adds the ApplyContinuousEffect 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 + """ + if isinstance(line, GenieVillagerGroup): + current_unit = line.get_units_with_command(command_id)[0] + + else: + current_unit = line.get_head_unit() + + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_name = command_lookup_dict[command_id][0] + + 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("engine.ability.type.ApplyContinuousEffect") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Ability properties + properties = {} + + # Get animation from commands proceed sprite + unit_commands = current_unit["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + if type_id != command_id: + continue + + ability_animation_id = command["proceed_sprite_id"].value + break + + else: + ability_animation_id = -1 + + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = create_animation( + line, + ability_animation_id, + property_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Create custom civ graphics + handled_graphics_set_ids = set() + for civ_group in dataset.civ_groups.values(): + civ = civ_group.civ + civ_id = civ_group.get_id() + + # Only proceed if the civ stores the unit in the line + if current_unit_id not in civ["units"].value.keys(): + continue + + civ_animation_id = civ["units"][current_unit_id]["attack_sprite_id"].value + + if civ_animation_id != ability_animation_id: + # Find the corresponding graphics set + graphics_set_id = -1 + for set_id, items in gset_lookup_dict.items(): + if civ_id in items[0]: + graphics_set_id = set_id + break + + # Check if the object for the animation has been created before + obj_exists = graphics_set_id in handled_graphics_set_ids + if not obj_exists: + handled_graphics_set_ids.add(graphics_set_id) + + obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}{ability_name}" + filename_prefix = (f"{command_lookup_dict[command_id][1]}_" + f"{gset_lookup_dict[graphics_set_id][2]}_") + create_civ_animation(line, + civ_group, + civ_animation_id, + property_ref, + obj_prefix, + filename_prefix, + obj_exists) + + # Command Sound + ability_comm_sound_id = current_unit["command_sound_id"].value + if ability_comm_sound_id > -1: + property_ref = f"{ability_ref}.CommandSound" + property_raw_api_object = RawAPIObject(property_ref, + "CommandSound", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + sounds_set = [] + sound_forward_ref = create_sound(line, + ability_comm_sound_id, + property_ref, + ability_name, + "command_") + sounds_set.append(sound_forward_ref) + property_raw_api_object.add_raw_member("sounds", sounds_set, + "engine.ability.property.type.CommandSound") + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + 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 + property_raw_api_object.add_raw_member("min_range", + min_range, + "engine.ability.property.type.Ranged") + + # Max range + if command_id == 105: + # Heal + max_range = 4 + + else: + 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 + }) + + # Effects + effects = None + allowed_types = None + if command_id == 101: + # Construct + effects = AoCEffectSubprocessor.get_construct_effects(line, ability_ref) + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( + ) + ] + + elif command_id == 105: + # Heal + effects = AoCEffectSubprocessor.get_heal_effects(line, ability_ref) + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() + ] + + elif command_id == 106: + # Repair + effects = AoCEffectSubprocessor.get_repair_effects(line, ability_ref) + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( + ) + ] + + ability_raw_api_object.add_raw_member("effects", + effects, + "engine.ability.type.ApplyContinuousEffect") + + # Application delay + apply_graphic = dataset.genie_graphics[ability_animation_id] + frame_rate = apply_graphic.get_frame_rate() + frame_delay = current_unit["frame_delay"].value + application_delay = frame_rate * frame_delay + ability_raw_api_object.add_raw_member("application_delay", + application_delay, + "engine.ability.type.ApplyContinuousEffect") + + # Allowed types + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.ApplyContinuousEffect") + ability_raw_api_object.add_raw_member("blacklisted_entities", + [], + "engine.ability.type.ApplyContinuousEffect") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/apply_discrete_effect.py b/openage/convert/processor/conversion/aoc/ability/apply_discrete_effect.py new file mode 100644 index 0000000000..f1ee0eed9f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/apply_discrete_effect.py @@ -0,0 +1,341 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ApplyDiscreteEffect ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..effect_subprocessor import AoCEffectSubprocessor +from .util import create_animation, create_civ_animation, create_sound + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def apply_discrete_effect_ability( + line: GenieGameEntityGroup, + command_id: int, + ranged: bool = False, + projectile: int = -1 +) -> ForwardRef: + """ + Adds the ApplyDiscreteEffect 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 + """ + if isinstance(line, GenieVillagerGroup): + current_unit = line.get_units_with_command(command_id)[0] + current_unit_id = current_unit["id0"].value + + else: + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + + head_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + ability_name = command_lookup_dict[command_id][0] + + 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("engine.ability.type.ApplyDiscreteEffect") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + ability_animation_id = current_unit["attack_sprite_id"].value + + else: + ability_ref = (f"{game_entity_name}.ShootProjectile.Projectile{projectile}." + f"{ability_name}") + ability_raw_api_object = RawAPIObject( + ability_ref, ability_name, dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.ApplyDiscreteEffect") + ability_location = ForwardRef( + line, + f"{game_entity_name}.ShootProjectile.Projectile{projectile}" + ) + ability_raw_api_object.set_location(ability_location) + + ability_animation_id = -1 + + line.add_raw_api_object(ability_raw_api_object) + + # Ability properties + properties = {} + + # Animated + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = create_animation( + line, + ability_animation_id, + property_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Create custom civ graphics + handled_graphics_set_ids = set() + for civ_group in dataset.civ_groups.values(): + civ = civ_group.civ + civ_id = civ_group.get_id() + + # Only proceed if the civ stores the unit in the line + if current_unit_id not in civ["units"].value.keys(): + continue + + civ_animation_id = civ["units"][current_unit_id]["attack_sprite_id"].value + + if civ_animation_id != ability_animation_id: + # Find the corresponding graphics set + graphics_set_id = -1 + for set_id, items in gset_lookup_dict.items(): + if civ_id in items[0]: + graphics_set_id = set_id + break + + # Check if the object for the animation has been created before + obj_exists = graphics_set_id in handled_graphics_set_ids + if not obj_exists: + handled_graphics_set_ids.add(graphics_set_id) + + obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}{ability_name}" + filename_prefix = (f"{command_lookup_dict[command_id][1]}_" + f"{gset_lookup_dict[graphics_set_id][2]}_") + create_civ_animation(line, + civ_group, + civ_animation_id, + property_ref, + obj_prefix, + filename_prefix, + obj_exists) + + # Command Sound + if projectile == -1: + ability_comm_sound_id = current_unit["command_sound_id"].value + + else: + ability_comm_sound_id = -1 + + if ability_comm_sound_id > -1: + property_ref = f"{ability_ref}.CommandSound" + property_raw_api_object = RawAPIObject(property_ref, + "CommandSound", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + sounds_set = [] + + if projectile == -1: + sound_obj_prefix = ability_name + + else: + sound_obj_prefix = "ProjectileAttack" + + sound_forward_ref = create_sound(line, + ability_comm_sound_id, + property_ref, + sound_obj_prefix, + "command_") + sounds_set.append(sound_forward_ref) + property_raw_api_object.add_raw_member("sounds", sounds_set, + "engine.ability.property.type.CommandSound") + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + 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 + 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 + 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" + batch_raw_api_object = RawAPIObject(batch_ref, "Batch", dataset.nyan_api_objects) + batch_raw_api_object.add_raw_parent("engine.util.effect_batch.type.UnorderedBatch") + batch_location = ForwardRef(line, ability_ref) + batch_raw_api_object.set_location(batch_location) + + line.add_raw_api_object(batch_raw_api_object) + + effects = None + if command_id == 7: + # Attack + if projectile != 1: + effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref) + + else: + effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref, projectile=1) + + elif command_id == 104: + # Convert + effects = AoCEffectSubprocessor.get_convert_effects(line, batch_ref) + + batch_raw_api_object.add_raw_member("effects", + effects, + "engine.util.effect_batch.EffectBatch") + + batch_forward_ref = ForwardRef(line, batch_ref) + ability_raw_api_object.add_raw_member("batches", + [batch_forward_ref], + "engine.ability.type.ApplyDiscreteEffect") + + # Reload time + if projectile == -1: + reload_time = current_unit["attack_speed"].value + + else: + reload_time = 0 + + ability_raw_api_object.add_raw_member("reload_time", + reload_time, + "engine.ability.type.ApplyDiscreteEffect") + + # Application delay + if projectile == -1: + attack_graphic_id = current_unit["attack_sprite_id"].value + attack_graphic = dataset.genie_graphics[attack_graphic_id] + frame_rate = attack_graphic.get_frame_rate() + frame_delay = current_unit["frame_delay"].value + application_delay = frame_rate * frame_delay + + else: + application_delay = 0 + + ability_raw_api_object.add_raw_member("application_delay", + application_delay, + "engine.ability.type.ApplyDiscreteEffect") + + # Allowed types (all buildings/units) + if command_id == 104: + # Convert + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() + ] + + else: + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( + ) + ] + + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.ApplyDiscreteEffect") + + if command_id == 104: + # Convert + blacklisted_entities = [] + for unit_line in dataset.unit_lines.values(): + if unit_line.has_command(104): + # Blacklist other monks + blacklisted_name = name_lookup_dict[unit_line.get_head_unit_id()][0] + blacklisted_entities.append(ForwardRef(unit_line, blacklisted_name)) + + elif unit_line.get_class_id() in (13, 55): + # Blacklist siege + blacklisted_name = name_lookup_dict[unit_line.get_head_unit_id()][0] + blacklisted_entities.append(ForwardRef(unit_line, blacklisted_name)) + + else: + blacklisted_entities = [] + + ability_raw_api_object.add_raw_member("blacklisted_entities", + blacklisted_entities, + "engine.ability.type.ApplyDiscreteEffect") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/attribute_change_tracker.py b/openage/convert/processor/conversion/aoc/ability/attribute_change_tracker.py new file mode 100644 index 0000000000..d1da844108 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/attribute_change_tracker.py @@ -0,0 +1,134 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the AttributeChangeTracker ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def attribute_change_tracker_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the AttributeChangeTracker 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + 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}.AttributeChangeTracker" + ability_raw_api_object = RawAPIObject(ability_ref, + "AttributeChangeTracker", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.AttributeChangeTracker") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Attribute + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + ability_raw_api_object.add_raw_member("attribute", + attribute, + "engine.ability.type.AttributeChangeTracker") + + # Change progress + damage_graphics = current_unit["damage_graphics"].value + progress_forward_refs = [] + + # Damage graphics are ordered ascending, so we start from 0 + interval_left_bound = 0 + for damage_graphic_member in damage_graphics: + interval_right_bound = damage_graphic_member["damage_percent"].value + progress_ref = f"{ability_ref}.ChangeProgress{interval_right_bound}" + progress_raw_api_object = RawAPIObject(progress_ref, + f"ChangeProgress{interval_right_bound}", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.AttributeChange"], + "engine.util.progress.Progress") + + # Interval + progress_raw_api_object.add_raw_member("left_boundary", + interval_left_bound, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + interval_right_bound, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # AnimationOverlay property + # ===================================================================================== + progress_animation_id = damage_graphic_member["graphic_id"].value + if progress_animation_id > -1: + property_ref = f"{progress_ref}.AnimationOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "AnimationOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.AnimationOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Animation + animations_set = [] + animation_forward_ref = create_animation( + line, + progress_animation_id, + property_ref, + "Idle", + f"idle_damage_override_{interval_right_bound}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("overlays", + animations_set, + "engine.util.progress.property.type.AnimationOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.AnimationOverlay"]: property_forward_ref + }) + + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + interval_left_bound = interval_right_bound + + ability_raw_api_object.add_raw_member("change_progress", + progress_forward_refs, + "engine.ability.type.AttributeChangeTracker") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/collect_storage.py b/openage/convert/processor/conversion/aoc/ability/collect_storage.py new file mode 100644 index 0000000000..654bcf4a86 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/collect_storage.py @@ -0,0 +1,65 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the CollectStorage ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def collect_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the CollectStorage 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}.CollectStorage" + ability_raw_api_object = RawAPIObject(ability_ref, + "CollectStorage", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.CollectStorage") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Container + container_ref = f"{game_entity_name}.Storage.{game_entity_name}Container" + container_forward_ref = ForwardRef(line, container_ref) + ability_raw_api_object.add_raw_member("container", + container_forward_ref, + "engine.ability.type.CollectStorage") + + # Storage elements + elements = [] + entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) + for entity in line.garrison_entities: + entity_ref = entity_lookups[entity.get_head_unit_id()][0] + entity_forward_ref = ForwardRef(entity, entity_ref) + elements.append(entity_forward_ref) + + ability_raw_api_object.add_raw_member("storage_elements", + elements, + "engine.ability.type.CollectStorage") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/collision.py b/openage/convert/processor/conversion/aoc/ability/collision.py new file mode 100644 index 0000000000..48c5c89e92 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/collision.py @@ -0,0 +1,74 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Collision ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def collision_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Collision 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 = line.get_head_unit() + 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}.Collision" + ability_raw_api_object = RawAPIObject(ability_ref, "Collision", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Collision") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Hitbox object + hitbox_name = f"{game_entity_name}.Collision.{game_entity_name}Hitbox" + hitbox_raw_api_object = RawAPIObject(hitbox_name, + f"{game_entity_name}Hitbox", + dataset.nyan_api_objects) + hitbox_raw_api_object.add_raw_parent("engine.util.hitbox.Hitbox") + hitbox_location = ForwardRef(line, ability_ref) + hitbox_raw_api_object.set_location(hitbox_location) + + radius_x = current_unit["radius_x"].value + radius_y = current_unit["radius_y"].value + radius_z = current_unit["radius_z"].value + + hitbox_raw_api_object.add_raw_member("radius_x", + radius_x, + "engine.util.hitbox.Hitbox") + hitbox_raw_api_object.add_raw_member("radius_y", + radius_y, + "engine.util.hitbox.Hitbox") + hitbox_raw_api_object.add_raw_member("radius_z", + radius_z, + "engine.util.hitbox.Hitbox") + + hitbox_forward_ref = ForwardRef(line, hitbox_name) + ability_raw_api_object.add_raw_member("hitbox", + hitbox_forward_ref, + "engine.ability.type.Collision") + + line.add_raw_api_object(hitbox_raw_api_object) + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/constructable.py b/openage/convert/processor/conversion/aoc/ability/constructable.py new file mode 100644 index 0000000000..f3dbde9431 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/constructable.py @@ -0,0 +1,1303 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Constructable ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def constructable_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Constructable 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + 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}.Constructable" + ability_raw_api_object = RawAPIObject( + ability_ref, "Constructable", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Constructable") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Starting progress (always 0) + ability_raw_api_object.add_raw_member("starting_progress", + 0, + "engine.ability.type.Constructable") + + construction_animation_id = current_unit["construction_graphic_id"].value + + # Construction progress + progress_forward_refs = [] + if line.get_class_id() == 49: + # Farms + # ===================================================================================== + progress_ref = f"{ability_ref}.ConstructionProgress0" + progress_raw_api_object = RawAPIObject(progress_ref, + "ConstructionProgress0", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Construct"], + "engine.util.progress.Progress") + + # Interval = (0.0, 0.0) + progress_raw_api_object.add_raw_member("left_boundary", + 0.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 0.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction1" + terrain_group = dataset.terrain_groups[29] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + # ===================================================================================== + init_state_name = f"{ability_ref}.InitState" + init_state_raw_api_object = RawAPIObject(init_state_name, + "InitState", + dataset.nyan_api_objects) + init_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") + init_state_location = ForwardRef(line, property_ref) + init_state_raw_api_object.set_location(init_state_location) + + line.add_raw_api_object(init_state_raw_api_object) + + # Priority + init_state_raw_api_object.add_raw_member("priority", + 1, + "engine.util.state_machine.StateChanger") + + # Enabled abilities + enabled_forward_refs = [ + ForwardRef(line, + f"{game_entity_name}.VisibilityConstruct0") + ] + init_state_raw_api_object.add_raw_member("enable_abilities", + enabled_forward_refs, + "engine.util.state_machine.StateChanger") + + # Disabled abilities + disabled_forward_refs = [ + ForwardRef(line, + f"{game_entity_name}.AttributeChangeTracker"), + ForwardRef(line, + f"{game_entity_name}.LineOfSight"), + ForwardRef(line, + f"{game_entity_name}.Visibility") + ] + if len(line.creates) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Create")) + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.ProductionQueue")) + + if len(line.researches) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Research")) + + if line.is_projectile_shooter(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Attack")) + + if line.is_garrison(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Storage")) + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RemoveStorage")) + + garrison_mode = line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.NATURAL: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.SendBackToTask")) + + if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RallyPoint")) + + if line.is_harvestable(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Harvestable")) + + if line.is_dropsite(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.DropSite")) + + if line.is_trade_post(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.TradePost")) + + init_state_raw_api_object.add_raw_member("disable_abilities", + disabled_forward_refs, + "engine.util.state_machine.StateChanger") + + # Enabled modifiers + init_state_raw_api_object.add_raw_member("enable_modifiers", + [], + "engine.util.state_machine.StateChanger") + + # Disabled modifiers + init_state_raw_api_object.add_raw_member("disable_modifiers", + [], + "engine.util.state_machine.StateChanger") + # ===================================================================================== + init_state_forward_ref = ForwardRef(line, init_state_name) + property_raw_api_object.add_raw_member("state_change", + init_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.ConstructionProgress33" + progress_raw_api_object = RawAPIObject(progress_ref, + "ConstructionProgress33", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Construct"], + "engine.util.progress.Progress") + + # Interval = (0.0, 33.0) + progress_raw_api_object.add_raw_member("left_boundary", + 0.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 33.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction1" + terrain_group = dataset.terrain_groups[29] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + # ===================================================================================== + construct_state_name = f"{ability_ref}.ConstructState" + construct_state_raw_api_object = RawAPIObject(construct_state_name, + "ConstructState", + dataset.nyan_api_objects) + construct_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") + construct_state_location = ForwardRef(line, ability_ref) + construct_state_raw_api_object.set_location(construct_state_location) + + line.add_raw_api_object(construct_state_raw_api_object) + + # Priority + construct_state_raw_api_object.add_raw_member("priority", + 1, + "engine.util.state_machine.StateChanger") + + # Enabled abilities + construct_state_raw_api_object.add_raw_member("enable_abilities", + [], + "engine.util.state_machine.StateChanger") + + # Disabled abilities + disabled_forward_refs = [ForwardRef(line, + f"{game_entity_name}.AttributeChangeTracker")] + if len(line.creates) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Create")) + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.ProductionQueue")) + if len(line.researches) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Research")) + + if line.is_projectile_shooter(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Attack")) + + if line.is_garrison(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Storage")) + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RemoveStorage")) + + garrison_mode = line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.NATURAL: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.SendBackToTask")) + + if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RallyPoint")) + + if line.is_harvestable(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Harvestable")) + + if line.is_dropsite(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.DropSite")) + + if line.is_trade_post(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.TradePost")) + + construct_state_raw_api_object.add_raw_member("disable_abilities", + disabled_forward_refs, + "engine.util.state_machine.StateChanger") + + # Enabled modifiers + construct_state_raw_api_object.add_raw_member("enable_modifiers", + [], + "engine.util.state_machine.StateChanger") + + # Disabled modifiers + construct_state_raw_api_object.add_raw_member("disable_modifiers", + [], + "engine.util.state_machine.StateChanger") + + # ===================================================================================== + construct_state_forward_ref = ForwardRef(line, construct_state_name) + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.ConstructionProgress66" + progress_raw_api_object = RawAPIObject(progress_ref, + "ConstructionProgress66", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Construct"], + "engine.util.progress.Progress") + + # Interval = (33.0, 66.0) + progress_raw_api_object.add_raw_member("left_boundary", + 33.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 66.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction2" + terrain_group = dataset.terrain_groups[30] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.ConstructionProgress100" + progress_raw_api_object = RawAPIObject(progress_ref, + "ConstructionProgress100", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Construct"], + "engine.util.progress.Progress") + + # Interval = (66.0, 100.0) + progress_raw_api_object.add_raw_member("left_boundary", + 66.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 100.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction3" + terrain_group = dataset.terrain_groups[31] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + + else: + progress_ref = f"{ability_ref}.ConstructionProgress0" + progress_raw_api_object = RawAPIObject(progress_ref, + "ConstructionProgress0", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Construct"], + "engine.util.progress.Progress") + + # Interval = (0.0, 0.0) + progress_raw_api_object.add_raw_member("left_boundary", + 0.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 0.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ================================================================================= + # Idle override + # ================================================================================= + if construction_animation_id > -1: + property_ref = f"{progress_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.Animated") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + overrides = [] + override_ref = f"{property_ref}.IdleOverride" + override_raw_api_object = RawAPIObject(override_ref, + "IdleOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, property_ref) + override_raw_api_object.set_location(override_location) + + line.add_raw_api_object(override_raw_api_object) + + idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") + override_raw_api_object.add_raw_member("ability", + idle_forward_ref, + "engine.util.animation_override.AnimationOverride") + + # Animation + animations_set = [] + animation_forward_ref = create_animation(line, + construction_animation_id, + override_ref, + "Idle", + "idle_construct0_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member("animations", + animations_set, + "engine.util.animation_override.AnimationOverride") + + override_raw_api_object.add_raw_member("priority", + 1, + "engine.util.animation_override.AnimationOverride") + + override_forward_ref = ForwardRef(line, override_ref) + overrides.append(override_forward_ref) + # ================================================================================= + property_raw_api_object.add_raw_member("overrides", + overrides, + "engine.util.progress.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref + }) + + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + # ===================================================================================== + init_state_name = f"{ability_ref}.InitState" + init_state_raw_api_object = RawAPIObject(init_state_name, + "InitState", + dataset.nyan_api_objects) + init_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") + init_state_location = ForwardRef(line, property_ref) + init_state_raw_api_object.set_location(init_state_location) + + line.add_raw_api_object(init_state_raw_api_object) + + # Priority + init_state_raw_api_object.add_raw_member("priority", + 1, + "engine.util.state_machine.StateChanger") + + # Enabled abilities + enabled_forward_refs = [ + ForwardRef(line, + f"{game_entity_name}.VisibilityConstruct0") + ] + init_state_raw_api_object.add_raw_member("enable_abilities", + enabled_forward_refs, + "engine.util.state_machine.StateChanger") + + # Disabled abilities + disabled_forward_refs = [ + ForwardRef(line, + f"{game_entity_name}.AttributeChangeTracker"), + ForwardRef(line, + f"{game_entity_name}.LineOfSight"), + ForwardRef(line, + f"{game_entity_name}.Visibility") + ] + if len(line.creates) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Create")) + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.ProductionQueue")) + if len(line.researches) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Research")) + + if line.is_projectile_shooter(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Attack")) + + if line.is_garrison(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Storage")) + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RemoveStorage")) + + garrison_mode = line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.NATURAL: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.SendBackToTask")) + + if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RallyPoint")) + + if line.is_harvestable(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Harvestable")) + + if line.is_dropsite(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.DropSite")) + + if line.is_trade_post(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.TradePost")) + + init_state_raw_api_object.add_raw_member("disable_abilities", + disabled_forward_refs, + "engine.util.state_machine.StateChanger") + + # Enabled modifiers + init_state_raw_api_object.add_raw_member("enable_modifiers", + [], + "engine.util.state_machine.StateChanger") + + # Disabled modifiers + init_state_raw_api_object.add_raw_member("disable_modifiers", + [], + "engine.util.state_machine.StateChanger") + # ===================================================================================== + init_state_forward_ref = ForwardRef(line, init_state_name) + property_raw_api_object.add_raw_member("state_change", + init_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.ConstructionProgress25" + progress_raw_api_object = RawAPIObject(progress_ref, + "ConstructionProgress25", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Construct"], + "engine.util.progress.Progress") + + # Interval = (0.0, 25.0) + progress_raw_api_object.add_raw_member("left_boundary", + 0.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 25.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ================================================================================= + # Idle override + # ================================================================================= + if construction_animation_id > -1: + property_ref = f"{progress_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.Animated") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + overrides = [] + override_ref = f"{progress_ref}.IdleOverride" + override_raw_api_object = RawAPIObject(override_ref, + "IdleOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, property_ref) + override_raw_api_object.set_location(override_location) + + line.add_raw_api_object(override_raw_api_object) + + idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") + override_raw_api_object.add_raw_member("ability", + idle_forward_ref, + "engine.util.animation_override.AnimationOverride") + + # Animation + animations_set = [] + animation_forward_ref = create_animation(line, + construction_animation_id, + override_ref, + "Idle", + "idle_construct25_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member("animations", + animations_set, + "engine.util.animation_override.AnimationOverride") + + override_raw_api_object.add_raw_member("priority", + 1, + "engine.util.animation_override.AnimationOverride") + + override_forward_ref = ForwardRef(line, override_ref) + overrides.append(override_forward_ref) + # ================================================================================= + property_raw_api_object.add_raw_member("overrides", + overrides, + "engine.util.progress.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref + }) + + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + # ===================================================================================== + construct_state_name = f"{ability_ref}.ConstructState" + construct_state_raw_api_object = RawAPIObject(construct_state_name, + "ConstructState", + dataset.nyan_api_objects) + construct_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") + construct_state_location = ForwardRef(line, property_ref) + construct_state_raw_api_object.set_location(construct_state_location) + + line.add_raw_api_object(construct_state_raw_api_object) + + # Priority + construct_state_raw_api_object.add_raw_member("priority", + 1, + "engine.util.state_machine.StateChanger") + + # Enabled abilities + construct_state_raw_api_object.add_raw_member("enable_abilities", + [], + "engine.util.state_machine.StateChanger") + + # Disabled abilities + disabled_forward_refs = [ForwardRef(line, + f"{game_entity_name}.AttributeChangeTracker")] + if len(line.creates) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Create")) + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.ProductionQueue")) + if len(line.researches) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Research")) + + if line.is_projectile_shooter(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Attack")) + + if line.is_garrison(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Storage")) + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RemoveStorage")) + + garrison_mode = line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.NATURAL: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.SendBackToTask")) + + if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RallyPoint")) + + if line.is_harvestable(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Harvestable")) + + if line.is_dropsite(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.DropSite")) + + if line.is_trade_post(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.TradePost")) + + construct_state_raw_api_object.add_raw_member("disable_abilities", + disabled_forward_refs, + "engine.util.state_machine.StateChanger") + + # Enabled modifiers + construct_state_raw_api_object.add_raw_member("enable_modifiers", + [], + "engine.util.state_machine.StateChanger") + + # Disabled modifiers + construct_state_raw_api_object.add_raw_member("disable_modifiers", + [], + "engine.util.state_machine.StateChanger") + # ===================================================================================== + construct_state_forward_ref = ForwardRef(line, construct_state_name) + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.ConstructionProgress50" + progress_raw_api_object = RawAPIObject(progress_ref, + "ConstructionProgress50", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Construct"], + "engine.util.progress.Progress") + + # Interval = (25.0, 50.0) + progress_raw_api_object.add_raw_member("left_boundary", + 25.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 50.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ================================================================================= + # Idle override + # ================================================================================= + if construction_animation_id > -1: + property_ref = f"{progress_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.Animated") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + overrides = [] + override_ref = f"{progress_ref}.IdleOverride" + override_raw_api_object = RawAPIObject(override_ref, + "IdleOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, property_ref) + override_raw_api_object.set_location(override_location) + + line.add_raw_api_object(override_raw_api_object) + + idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") + override_raw_api_object.add_raw_member("ability", + idle_forward_ref, + "engine.util.animation_override.AnimationOverride") + + # Animation + animations_set = [] + animation_forward_ref = create_animation(line, + construction_animation_id, + override_ref, + "Idle", + "idle_construct50_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member("animations", + animations_set, + "engine.util.animation_override.AnimationOverride") + + override_raw_api_object.add_raw_member("priority", + 1, + "engine.util.animation_override.AnimationOverride") + + override_forward_ref = ForwardRef(line, override_ref) + overrides.append(override_forward_ref) + # ================================================================================= + property_raw_api_object.add_raw_member("overrides", + overrides, + "engine.util.progress.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref + }) + + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.ConstructionProgress75" + progress_raw_api_object = RawAPIObject(progress_ref, + "ConstructionProgress75", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Construct"], + "engine.util.progress.Progress") + + # Interval = (50.0, 75.0) + progress_raw_api_object.add_raw_member("left_boundary", + 50.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 75.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ================================================================================= + # Idle override + # ================================================================================= + if construction_animation_id > -1: + property_ref = f"{progress_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.Animated") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + overrides = [] + override_ref = f"{progress_ref}.IdleOverride" + override_raw_api_object = RawAPIObject(override_ref, + "IdleOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, property_ref) + override_raw_api_object.set_location(override_location) + + line.add_raw_api_object(override_raw_api_object) + + idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") + override_raw_api_object.add_raw_member("ability", + idle_forward_ref, + "engine.util.animation_override.AnimationOverride") + + # Animation + animations_set = [] + animation_forward_ref = create_animation(line, + construction_animation_id, + override_ref, + "Idle", + "idle_construct75_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member("animations", + animations_set, + "engine.util.animation_override.AnimationOverride") + + override_raw_api_object.add_raw_member("priority", + 1, + "engine.util.animation_override.AnimationOverride") + + override_forward_ref = ForwardRef(line, override_ref) + overrides.append(override_forward_ref) + # ================================================================================= + property_raw_api_object.add_raw_member("overrides", + overrides, + "engine.util.progress.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref + }) + + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.ConstructionProgress100" + progress_raw_api_object = RawAPIObject(progress_ref, + "ConstructionProgress100", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Construct"], + "engine.util.progress.Progress") + + # Interval = (75.0, 100.0) + progress_raw_api_object.add_raw_member("left_boundary", + 75.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 100.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ================================================================================= + # Idle override + # ================================================================================= + if construction_animation_id > -1: + property_ref = f"{progress_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.Animated") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + overrides = [] + override_ref = f"{progress_ref}.IdleOverride" + override_raw_api_object = RawAPIObject(override_ref, + "IdleOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, progress_ref) + override_raw_api_object.set_location(override_location) + + line.add_raw_api_object(override_raw_api_object) + + idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") + override_raw_api_object.add_raw_member("ability", + idle_forward_ref, + "engine.util.animation_override.AnimationOverride") + + # Animation + animations_set = [] + animation_forward_ref = create_animation(line, + construction_animation_id, + override_ref, + "Idle", + "idle_construct100_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member("animations", + animations_set, + "engine.util.animation_override.AnimationOverride") + + override_raw_api_object.add_raw_member("priority", + 1, + "engine.util.animation_override.AnimationOverride") + + override_forward_ref = ForwardRef(line, override_ref) + overrides.append(override_forward_ref) + # ================================================================================= + property_raw_api_object.add_raw_member("overrides", + overrides, + "engine.util.progress.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref + }) + + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + ability_raw_api_object.add_raw_member("construction_progress", + progress_forward_refs, + "engine.ability.type.Constructable") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/create.py b/openage/convert/processor/conversion/aoc/ability/create.py new file mode 100644 index 0000000000..3cd5de8f33 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/create.py @@ -0,0 +1,89 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Create ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def create_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Create 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 + api_objects = dataset.nyan_api_objects + + 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}.Create" + ability_raw_api_object = RawAPIObject(ability_ref, "Create", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Create") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties = { + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + } + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Creatables + creatables_set = [] + for creatable in line.creates: + if creatable.is_unique(): + # Skip this because unique units are handled by civs + continue + + # CreatableGameEntity objects are created for each unit/building + # line individually to avoid duplicates. We just point to the + # raw API objects here. + creatable_id = creatable.get_head_unit_id() + creatable_name = name_lookup_dict[creatable_id][0] + + raw_api_object_ref = f"{creatable_name}.CreatableGameEntity" + creatable_forward_ref = ForwardRef(creatable, + raw_api_object_ref) + creatables_set.append(creatable_forward_ref) + + ability_raw_api_object.add_raw_member("creatables", creatables_set, + "engine.ability.type.Create") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/death.py b/openage/convert/processor/conversion/aoc/ability/death.py new file mode 100644 index 0000000000..f837ed994f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/death.py @@ -0,0 +1,296 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for death via the PassiveTransformTo ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode, \ + GenieUnitLineGroup, GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation, create_civ_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def death_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds a PassiveTransformTo ability to a line that is used to make entities die + based on a condition. + + :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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.Death" + ability_raw_api_object = RawAPIObject(ability_ref, "Death", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.PassiveTransformTo") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Ability properties + properties = {} + + # Animation + ability_animation_id = current_unit["dying_graphic"].value + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = create_animation(line, + ability_animation_id, + ability_ref, + "Death", + "death_") + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Create custom civ graphics + handled_graphics_set_ids = set() + for civ_group in dataset.civ_groups.values(): + civ = civ_group.civ + civ_id = civ_group.get_id() + + # Only proceed if the civ stores the unit in the line + if current_unit_id not in civ["units"].value.keys(): + continue + + civ_animation_id = civ["units"][current_unit_id]["dying_graphic"].value + + if civ_animation_id != ability_animation_id: + # Find the corresponding graphics set + graphics_set_id = -1 + for set_id, items in gset_lookup_dict.items(): + if civ_id in items[0]: + graphics_set_id = set_id + break + + # Check if the object for the animation has been created before + obj_exists = graphics_set_id in handled_graphics_set_ids + if not obj_exists: + handled_graphics_set_ids.add(graphics_set_id) + + obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}Death" + filename_prefix = f"death_{gset_lookup_dict[graphics_set_id][2]}_" + create_civ_animation(line, + civ_group, + civ_animation_id, + property_ref, + obj_prefix, + filename_prefix, + obj_exists) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Death condition + death_condition = [ + dataset.pregen_nyan_objects["util.logic.literal.death.StandardHealthDeathLiteral"].get_nyan_object( + ) + ] + ability_raw_api_object.add_raw_member("condition", + death_condition, + "engine.ability.type.PassiveTransformTo") + + # Transform time + # Use the time of the dying graphics + if ability_animation_id > -1: + dying_animation = dataset.genie_graphics[ability_animation_id] + death_time = dying_animation.get_animation_length() + + else: + death_time = 0.0 + + ability_raw_api_object.add_raw_member("transform_time", + death_time, + "engine.ability.type.PassiveTransformTo") + + # Target state + # ===================================================================================== + target_state_name = f"{game_entity_name}.Death.DeadState" + target_state_raw_api_object = RawAPIObject(target_state_name, + "DeadState", + dataset.nyan_api_objects) + target_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") + target_state_location = ForwardRef(line, ability_ref) + target_state_raw_api_object.set_location(target_state_location) + + # Priority + target_state_raw_api_object.add_raw_member("priority", + 1000, + "engine.util.state_machine.StateChanger") + + # Enabled abilities + target_state_raw_api_object.add_raw_member("enable_abilities", + [], + "engine.util.state_machine.StateChanger") + + # Disabled abilities + disabled_forward_refs = [] + if isinstance(line, (GenieUnitLineGroup, GenieBuildingLineGroup)): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.LineOfSight")) + + if isinstance(line, GenieBuildingLineGroup): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.AttributeChangeTracker")) + + if len(line.creates) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Create")) + + if isinstance(line, GenieBuildingLineGroup): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.ProductionQueue")) + if len(line.researches) > 0: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Research")) + + if line.is_projectile_shooter(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Attack")) + + if line.is_garrison(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Storage")) + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RemoveStorage")) + + garrison_mode = line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.NATURAL: + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.SendBackToTask")) + + if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.RallyPoint")) + + if line.is_harvestable(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Harvestable")) + + if isinstance(line, GenieBuildingLineGroup) and line.is_dropsite(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.DropSite")) + + if isinstance(line, GenieBuildingLineGroup) and line.is_trade_post(): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.TradePost")) + + target_state_raw_api_object.add_raw_member("disable_abilities", + disabled_forward_refs, + "engine.util.state_machine.StateChanger") + + # Enabled modifiers + target_state_raw_api_object.add_raw_member("enable_modifiers", + [], + "engine.util.state_machine.StateChanger") + + # Disabled modifiers + target_state_raw_api_object.add_raw_member("disable_modifiers", + [], + "engine.util.state_machine.StateChanger") + + line.add_raw_api_object(target_state_raw_api_object) + # ===================================================================================== + target_state_forward_ref = ForwardRef(line, target_state_name) + ability_raw_api_object.add_raw_member("target_state", + target_state_forward_ref, + "engine.ability.type.PassiveTransformTo") + + # Transform progress + # ===================================================================================== + progress_ref = f"{ability_ref}.DeathProgress" + progress_raw_api_object = RawAPIObject(progress_ref, + "DeathProgress", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.AttributeChange"], + "engine.util.progress.Progress") + + # Interval = (0.0, 100.0) + progress_raw_api_object.add_raw_member("left_boundary", + 0.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 100.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change = target state + property_raw_api_object.add_raw_member("state_change", + target_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + # ===================================================================================== + progress_forward_ref = ForwardRef(line, progress_ref) + ability_raw_api_object.add_raw_member("transform_progress", + [progress_forward_ref], + "engine.ability.type.PassiveTransformTo") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/delete.py b/openage/convert/processor/conversion/aoc/ability/delete.py new file mode 100644 index 0000000000..cc7b316b7f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/delete.py @@ -0,0 +1,126 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for deleting a unit via the PassiveTransformTo ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def delete_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds a PassiveTransformTo ability to a line that is used to make entities deletable. + + :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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + 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}.Delete" + ability_raw_api_object = RawAPIObject(ability_ref, "Delete", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.ActiveTransformTo") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Ability properties + properties = {} + + # Animation + ability_animation_id = current_unit["dying_graphic"].value + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Use the animation from Death ability + animations_set = [] + animation_ref = f"{game_entity_name}.Death.DeathAnimation" + animation_forward_ref = ForwardRef(line, animation_ref) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Transform time + # Use the time of the dying graphics + if ability_animation_id > -1: + dying_animation = dataset.genie_graphics[ability_animation_id] + death_time = dying_animation.get_animation_length() + + else: + death_time = 0.0 + + ability_raw_api_object.add_raw_member("transform_time", + death_time, + "engine.ability.type.ActiveTransformTo") + + # Target state (reuse from Death) + target_state_ref = f"{game_entity_name}.Death.DeadState" + target_state_forward_ref = ForwardRef(line, target_state_ref) + ability_raw_api_object.add_raw_member("target_state", + target_state_forward_ref, + "engine.ability.type.ActiveTransformTo") + + # Transform progress (reuse from Death) + progress_ref = f"{game_entity_name}.Death.DeathProgress" + progress_forward_ref = ForwardRef(line, progress_ref) + ability_raw_api_object.add_raw_member("transform_progress", + [progress_forward_ref], + "engine.ability.type.ActiveTransformTo") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/despawn.py b/openage/convert/processor/conversion/aoc/ability/despawn.py new file mode 100644 index 0000000000..6e9319cc25 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/despawn.py @@ -0,0 +1,167 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for deleting a unit via the PassiveTransformTo ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation, create_civ_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def despawn_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Despawn 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + # Animation and time come from dead unit + death_animation_id = current_unit["dying_graphic"].value + dead_unit_id = current_unit["dead_unit_id"].value + dead_unit = None + if dead_unit_id > -1: + dead_unit = dataset.genie_units[dead_unit_id] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.Despawn" + ability_raw_api_object = RawAPIObject(ability_ref, "Despawn", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Despawn") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Ability properties + properties = {} + + # Animation + ability_animation_id = -1 + if dead_unit: + ability_animation_id = dead_unit["idle_graphic0"].value + + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = create_animation(line, + ability_animation_id, + property_ref, + "Despawn", + "despawn_") + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", + animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Create custom civ graphics + handled_graphics_set_ids = set() + for civ_group in dataset.civ_groups.values(): + civ = civ_group.civ + civ_id = civ_group.get_id() + + # Only proceed if the civ stores the unit in the line + if current_unit_id not in civ["units"].value.keys(): + continue + + civ_unit = civ["units"][current_unit_id] + civ_dead_unit_id = civ_unit["dead_unit_id"].value + civ_dead_unit = None + if civ_dead_unit_id > -1: + civ_dead_unit = dataset.genie_units[civ_dead_unit_id] + + civ_animation_id = civ_dead_unit["idle_graphic0"].value + + if civ_animation_id != ability_animation_id: + # Find the corresponding graphics set + graphics_set_id = -1 + for set_id, items in gset_lookup_dict.items(): + if civ_id in items[0]: + graphics_set_id = set_id + break + + # Check if the object for the animation has been created before + obj_exists = graphics_set_id in handled_graphics_set_ids + if not obj_exists: + handled_graphics_set_ids.add(graphics_set_id) + + obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}Despawn" + filename_prefix = f"despawn_{gset_lookup_dict[graphics_set_id][2]}_" + create_civ_animation(line, + civ_group, + civ_animation_id, + property_ref, + obj_prefix, + filename_prefix, + obj_exists) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Activation condition + # Uses the death condition of the units + activation_condition = [ + dataset.pregen_nyan_objects["util.logic.literal.death.StandardHealthDeathLiteral"].get_nyan_object( + ) + ] + ability_raw_api_object.add_raw_member("activation_condition", + activation_condition, + "engine.ability.type.Despawn") + + # Despawn condition + ability_raw_api_object.add_raw_member("despawn_condition", + [], + "engine.ability.type.Despawn") + + # Despawn time = corpse decay time (dead unit) or Death animation time (if no dead unit exist) + despawn_time = 0 + if dead_unit: + resource_storage = dead_unit["resource_storage"].value + for storage in resource_storage: + resource_id = storage["type"].value + + if resource_id == 12: + despawn_time = storage["amount"].value + + elif death_animation_id > -1: + despawn_time = dataset.genie_graphics[death_animation_id].get_animation_length() + + ability_raw_api_object.add_raw_member("despawn_time", + despawn_time, + "engine.ability.type.Despawn") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/drop_resources.py b/openage/convert/processor/conversion/aoc/ability/drop_resources.py new file mode 100644 index 0000000000..85adc849a6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/drop_resources.py @@ -0,0 +1,125 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the DropResources ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue + +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def drop_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the DropResources 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 + """ + if isinstance(line, GenieVillagerGroup): + gatherers = line.variants[0].line + + else: + gatherers = [line.line[0]] + + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.DropResources" + ability_raw_api_object = RawAPIObject(ability_ref, + "DropResources", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.DropResources") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Resource containers + containers = [] + for gatherer in gatherers: + unit_commands = gatherer["unit_commands"].value + + for command in unit_commands: + # Find a gather ability. It doesn't matter which one because + # they should all produce the same resource for one genie unit. + type_id = command["type"].value + + if type_id in (5, 110): + break + + gatherer_unit_id = gatherer.get_id() + if gatherer_unit_id not in gather_lookup_dict: + # Skips hunting wolves + continue + + container_ref = (f"{game_entity_name}.ResourceStorage." + f"{gather_lookup_dict[gatherer_unit_id][0]}Container") + container_forward_ref = ForwardRef(line, container_ref) + containers.append(container_forward_ref) + + ability_raw_api_object.add_raw_member("containers", + containers, + "engine.ability.type.DropResources") + + # Search range + ability_raw_api_object.add_raw_member("search_range", + MemberSpecialValue.NYAN_INF, + "engine.ability.type.DropResources") + + # Allowed types + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.DropSite"].get_nyan_object() + ] + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.DropResources") + # Blacklisted enties + ability_raw_api_object.add_raw_member("blacklisted_entities", + [], + "engine.ability.type.DropResources") + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties = { + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + } + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/drop_site.py b/openage/convert/processor/conversion/aoc/ability/drop_site.py new file mode 100644 index 0000000000..68f4851262 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/drop_site.py @@ -0,0 +1,92 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the DropResources ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def drop_site_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the DropSite 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 + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.DropSite" + ability_raw_api_object = RawAPIObject(ability_ref, "DropSite", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.DropSite") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Resource containers + gatherer_ids = line.get_gatherer_ids() + + containers = [] + for gatherer_id in gatherer_ids: + if gatherer_id not in gather_lookup_dict: + # Skips hunting wolves + continue + + gatherer_line = dataset.unit_ref[gatherer_id] + gatherer_head_unit_id = gatherer_line.get_head_unit_id() + gatherer_name = name_lookup_dict[gatherer_head_unit_id][0] + + container_ref = (f"{gatherer_name}.ResourceStorage." + f"{gather_lookup_dict[gatherer_id][0]}Container") + container_forward_ref = ForwardRef(gatherer_line, container_ref) + containers.append(container_forward_ref) + + ability_raw_api_object.add_raw_member("accepts_from", + containers, + "engine.ability.type.DropSite") + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties = { + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + } + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/enter_container.py b/openage/convert/processor/conversion/aoc/ability/enter_container.py new file mode 100644 index 0000000000..3efa7fe651 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/enter_container.py @@ -0,0 +1,83 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the EnterContainer ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def enter_container_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the EnterContainer 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. None if no valid containers were found. + :rtype: ...dataformat.forward_ref.ForwardRef, None + """ + 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}.EnterContainer" + ability_raw_api_object = RawAPIObject(ability_ref, + "EnterContainer", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.EnterContainer") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Containers + containers = [] + entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) + for garrison in line.garrison_locations: + garrison_mode = garrison.get_garrison_mode() + + # Cannot enter production buildings or monk inventories + if garrison_mode in (GenieGarrisonMode.SELF_PRODUCED, GenieGarrisonMode.MONK): + continue + + garrison_name = entity_lookups[garrison.get_head_unit_id()][0] + + container_ref = f"{garrison_name}.Storage.{garrison_name}Container" + container_forward_ref = ForwardRef(garrison, container_ref) + containers.append(container_forward_ref) + + if not containers: + return None + + ability_raw_api_object.add_raw_member("allowed_containers", + containers, + "engine.ability.type.EnterContainer") + + # Allowed types (all buildings/units) + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object() + ] + + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.EnterContainer") + ability_raw_api_object.add_raw_member("blacklisted_entities", + [], + "engine.ability.type.EnterContainer") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/exchange_resources.py b/openage/convert/processor/conversion/aoc/ability/exchange_resources.py new file mode 100644 index 0000000000..2dc2f0383f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/exchange_resources.py @@ -0,0 +1,82 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ExchangeContainer ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def exchange_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ExchangeResources 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] + + resource_names = ["Food", "Wood", "Stone"] + + abilities = [] + for resource_name in resource_names: + ability_name = f"MarketExchange{resource_name}" + 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("engine.ability.type.ExchangeResources") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Resource that is exchanged (resource A) + resource_a = dataset.pregen_nyan_objects[f"util.resource.types.{resource_name}"].get_nyan_object( + ) + ability_raw_api_object.add_raw_member("resource_a", + resource_a, + "engine.ability.type.ExchangeResources") + + # Resource that is exchanged for (resource B) + resource_b = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() + ability_raw_api_object.add_raw_member("resource_b", + resource_b, + "engine.ability.type.ExchangeResources") + + # Exchange rate + exchange_rate_ref = f"util.resource.market_trading.Market{resource_name}ExchangeRate" + exchange_rate = dataset.pregen_nyan_objects[exchange_rate_ref].get_nyan_object() + ability_raw_api_object.add_raw_member("exchange_rate", + exchange_rate, + "engine.ability.type.ExchangeResources") + + # Exchange modes + buy_exchange_ref = "util.resource.market_trading.MarketBuyExchangeMode" + sell_exchange_ref = "util.resource.market_trading.MarketSellExchangeMode" + exchange_modes = [ + dataset.pregen_nyan_objects[buy_exchange_ref].get_nyan_object(), + dataset.pregen_nyan_objects[sell_exchange_ref].get_nyan_object(), + ] + ability_raw_api_object.add_raw_member("exchange_modes", + exchange_modes, + "engine.ability.type.ExchangeResources") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + abilities.append(ability_forward_ref) + + return abilities diff --git a/openage/convert/processor/conversion/aoc/ability/exit_container.py b/openage/convert/processor/conversion/aoc/ability/exit_container.py new file mode 100644 index 0000000000..f5065ee5ea --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/exit_container.py @@ -0,0 +1,70 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ExitContainer ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def exit_container_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ExitContainer 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. None if no valid containers were found. + :rtype: ...dataformat.forward_ref.ForwardRef, None + """ + 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}.ExitContainer" + ability_raw_api_object = RawAPIObject(ability_ref, + "ExitContainer", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.ExitContainer") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Containers + containers = [] + entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) + for garrison in line.garrison_locations: + garrison_mode = garrison.get_garrison_mode() + + # Cannot enter production buildings or monk inventories + if garrison_mode == GenieGarrisonMode.MONK: + continue + + garrison_name = entity_lookups[garrison.get_head_unit_id()][0] + + container_ref = f"{garrison_name}.Storage.{garrison_name}Container" + container_forward_ref = ForwardRef(garrison, container_ref) + containers.append(container_forward_ref) + + if not containers: + return None + + ability_raw_api_object.add_raw_member("allowed_containers", + containers, + "engine.ability.type.ExitContainer") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/formation.py b/openage/convert/processor/conversion/aoc/ability/formation.py new file mode 100644 index 0000000000..718fa487e6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/formation.py @@ -0,0 +1,98 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Formation ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def formation_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Formation 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}.Formation" + ability_raw_api_object = RawAPIObject(ability_ref, "Formation", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Formation") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Formation definitions + if line.get_class_id() in (6,): + subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Infantry"].get_nyan_object( + ) + + elif line.get_class_id() in (12, 47): + subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Cavalry"].get_nyan_object( + ) + + elif line.get_class_id() in (0, 23, 36, 44, 55): + subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Ranged"].get_nyan_object( + ) + + elif line.get_class_id() in (2, 13, 18, 20, 35, 43, 51, 59): + subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Siege"].get_nyan_object( + ) + + else: + subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Support"].get_nyan_object( + ) + + formation_names = ["Line", "Staggered", "Box", "Flank"] + + formation_defs = [] + for formation_name in formation_names: + ge_formation_ref = f"{game_entity_name}.Formation.{formation_name}" + ge_formation_raw_api_object = RawAPIObject(ge_formation_ref, + formation_name, + dataset.nyan_api_objects) + ge_formation_raw_api_object.add_raw_parent( + "engine.util.game_entity_formation.GameEntityFormation") + ge_formation_location = ForwardRef(line, ability_ref) + ge_formation_raw_api_object.set_location(ge_formation_location) + + # Formation + formation_ref = f"util.formation.types.{formation_name}" + formation = dataset.pregen_nyan_objects[formation_ref].get_nyan_object() + ge_formation_raw_api_object.add_raw_member("formation", + formation, + "engine.util.game_entity_formation.GameEntityFormation") + + # Subformation + ge_formation_raw_api_object.add_raw_member("subformation", + subformation, + "engine.util.game_entity_formation.GameEntityFormation") + + line.add_raw_api_object(ge_formation_raw_api_object) + ge_formation_forward_ref = ForwardRef(line, ge_formation_ref) + formation_defs.append(ge_formation_forward_ref) + + ability_raw_api_object.add_raw_member("formations", + formation_defs, + "engine.ability.type.Formation") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/foundation.py b/openage/convert/processor/conversion/aoc/ability/foundation.py new file mode 100644 index 0000000000..f46c04a271 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/foundation.py @@ -0,0 +1,59 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Foundation ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def foundation_ability(line: GenieGameEntityGroup, terrain_id: int = -1) -> ForwardRef: + """ + Adds the Foundation abilities to a line. Optionally chooses the specified + terrain ID. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param terrain_id: Force this terrain ID as foundation + :type terrain_id: int + :returns: The forward references for the abilities. + :rtype: ...dataformat.forward_ref.ForwardRef + """ + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.Foundation" + ability_raw_api_object = RawAPIObject(ability_ref, "Foundation", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Foundation") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Terrain + if terrain_id == -1: + terrain_id = current_unit["foundation_terrain_id"].value + + terrain = dataset.terrain_groups[terrain_id] + terrain_forward_ref = ForwardRef(terrain, terrain_lookup_dict[terrain_id][1]) + ability_raw_api_object.add_raw_member("foundation_terrain", + terrain_forward_ref, + "engine.ability.type.Foundation") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/game_entity_stance.py b/openage/convert/processor/conversion/aoc/ability/game_entity_stance.py new file mode 100644 index 0000000000..0a85843614 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/game_entity_stance.py @@ -0,0 +1,102 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the GameEntityStance ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def game_entity_stance_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the GameEntityStance 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 = line.get_head_unit() + 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}.GameEntityStance" + ability_raw_api_object = RawAPIObject(ability_ref, + "GameEntityStance", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.GameEntityStance") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Stances + search_range = current_unit["search_radius"].value + stance_names = ["Aggressive", "Defensive", "StandGround", "Passive"] + + # Attacking is prefered + ability_preferences = [] + if line.is_projectile_shooter(): + ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Attack")) + + elif line.is_melee() or line.is_ranged(): + if line.has_command(7): + ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Attack")) + + if line.has_command(105): + ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Heal")) + + # Units are prefered before buildings + type_preferences = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), + ] + + stances = [] + for stance_name in stance_names: + stance_api_ref = f"engine.util.game_entity_stance.type.{stance_name}" + + stance_ref = f"{game_entity_name}.GameEntityStance.{stance_name}" + stance_raw_api_object = RawAPIObject(stance_ref, stance_name, dataset.nyan_api_objects) + stance_raw_api_object.add_raw_parent(stance_api_ref) + stance_location = ForwardRef(line, ability_ref) + stance_raw_api_object.set_location(stance_location) + + # Search range + stance_raw_api_object.add_raw_member("search_range", + search_range, + "engine.util.game_entity_stance.GameEntityStance") + + # Ability preferences + stance_raw_api_object.add_raw_member("ability_preference", + ability_preferences, + "engine.util.game_entity_stance.GameEntityStance") + + # Type preferences + stance_raw_api_object.add_raw_member("type_preference", + type_preferences, + "engine.util.game_entity_stance.GameEntityStance") + + line.add_raw_api_object(stance_raw_api_object) + stance_forward_ref = ForwardRef(line, stance_ref) + stances.append(stance_forward_ref) + + ability_raw_api_object.add_raw_member("stances", + stances, + "engine.ability.type.GameEntityStance") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/gather.py b/openage/convert/processor/conversion/aoc/ability/gather.py new file mode 100644 index 0000000000..6a23d73ae8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/gather.py @@ -0,0 +1,256 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Gather ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from ......util.ordered_set import OrderedSet +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def gather_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Gather abilities to a line. Unlike the other methods, this + creates multiple abilities. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :returns: The forward references for the abilities. + :rtype: list + """ + if isinstance(line, GenieVillagerGroup): + gatherers = line.variants[0].line + + else: + gatherers = [line.line[0]] + + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + abilities = [] + for gatherer in gatherers: + unit_commands = gatherer["unit_commands"].value + resource = None + ability_animation_id = -1 + harvestable_class_ids = OrderedSet() + harvestable_unit_ids = OrderedSet() + + for command in unit_commands: + # Find a gather ability. It doesn't matter which one because + # they should all produce the same resource for one genie unit. + type_id = command["type"].value + + if type_id not in (5, 110): + continue + + target_class_id = command["class_id"].value + if target_class_id > -1: + harvestable_class_ids.add(target_class_id) + + target_unit_id = command["unit_id"].value + if target_unit_id > -1: + harvestable_unit_ids.add(target_unit_id) + + resource_id = command["resource_out"].value + + # If resource_out is not specified, the gatherer harvests resource_in + if resource_id == -1: + resource_id = command["resource_in"].value + + if resource_id == 0: + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object( + ) + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object( + ) + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( + ) + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object( + ) + + else: + continue + + if type_id == 110: + ability_animation_id = command["work_sprite_id"].value + + else: + ability_animation_id = command["proceed_sprite_id"].value + + # Look for the harvestable groups that match the class IDs and unit IDs + check_groups = [] + check_groups.extend(dataset.unit_lines.values()) + check_groups.extend(dataset.building_lines.values()) + check_groups.extend(dataset.ambient_groups.values()) + + harvestable_groups = [] + for group in check_groups: + if not group.is_harvestable(): + continue + + if group.get_class_id() in harvestable_class_ids: + harvestable_groups.append(group) + continue + + for unit_id in harvestable_unit_ids: + if group.contains_entity(unit_id): + harvestable_groups.append(group) + + if len(harvestable_groups) == 0: + # If no matching groups are found, then we don't + # need to create an ability. + continue + + gatherer_unit_id = gatherer.get_id() + if gatherer_unit_id not in gather_lookup_dict: + # Skips hunting wolves + continue + + ability_name = gather_lookup_dict[gatherer_unit_id][0] + + 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("engine.ability.type.Gather") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Ability properties + properties = {} + + # Animation + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = create_animation( + line, + ability_animation_id, + property_ref, + ability_name, + f"{gather_lookup_dict[gatherer_unit_id][1]}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [ + dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Auto resume + ability_raw_api_object.add_raw_member("auto_resume", + True, + "engine.ability.type.Gather") + + # search range + ability_raw_api_object.add_raw_member("resume_search_range", + MemberSpecialValue.NYAN_INF, + "engine.ability.type.Gather") + + # Gather rate + rate_name = f"{game_entity_name}.{ability_name}.GatherRate" + rate_raw_api_object = RawAPIObject(rate_name, "GatherRate", dataset.nyan_api_objects) + rate_raw_api_object.add_raw_parent("engine.util.resource.ResourceRate") + rate_location = ForwardRef(line, ability_ref) + rate_raw_api_object.set_location(rate_location) + + rate_raw_api_object.add_raw_member( + "type", resource, "engine.util.resource.ResourceRate") + + gather_rate = gatherer["work_rate"].value + rate_raw_api_object.add_raw_member( + "rate", gather_rate, "engine.util.resource.ResourceRate") + + line.add_raw_api_object(rate_raw_api_object) + + rate_forward_ref = ForwardRef(line, rate_name) + ability_raw_api_object.add_raw_member("gather_rate", + rate_forward_ref, + "engine.ability.type.Gather") + + # Resource container + container_ref = (f"{game_entity_name}.ResourceStorage." + f"{gather_lookup_dict[gatherer_unit_id][0]}Container") + container_forward_ref = ForwardRef(line, container_ref) + ability_raw_api_object.add_raw_member("container", + container_forward_ref, + "engine.ability.type.Gather") + + # Targets (resource spots) + entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) + spot_forward_refs = [] + for group in harvestable_groups: + group_id = group.get_head_unit_id() + group_name = entity_lookups[group_id][0] + + spot_forward_ref = ForwardRef(group, + f"{group_name}.Harvestable.{group_name}ResourceSpot") + spot_forward_refs.append(spot_forward_ref) + + ability_raw_api_object.add_raw_member("targets", + spot_forward_refs, + "engine.ability.type.Gather") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + abilities.append(ability_forward_ref) + + return abilities diff --git a/openage/convert/processor/conversion/aoc/ability/harvestable.py b/openage/convert/processor/conversion/aoc/ability/harvestable.py new file mode 100644 index 0000000000..8e312b7f75 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/harvestable.py @@ -0,0 +1,397 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Harvestable ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def harvestable_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Harvestable 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + 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}.Harvestable" + ability_raw_api_object = RawAPIObject(ability_ref, "Harvestable", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Harvestable") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Resource spot + resource_storage = current_unit["resource_storage"].value + + for storage in resource_storage: + resource_id = storage["type"].value + + # IDs 15, 16, 17 are other types of food (meat, berries, fish) + if resource_id in (0, 15, 16, 17): + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( + ) + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() + + else: + continue + + spot_name = f"{game_entity_name}.Harvestable.{game_entity_name}ResourceSpot" + spot_raw_api_object = RawAPIObject(spot_name, + f"{game_entity_name}ResourceSpot", + dataset.nyan_api_objects) + spot_raw_api_object.add_raw_parent("engine.util.resource_spot.ResourceSpot") + spot_location = ForwardRef(line, ability_ref) + spot_raw_api_object.set_location(spot_location) + + # Type + spot_raw_api_object.add_raw_member("resource", + resource, + "engine.util.resource_spot.ResourceSpot") + + # Start amount (equals max amount) + if line.get_id() == 50: + # Farm food amount (hardcoded in civ) + starting_amount = dataset.genie_civs[1]["resources"][36].value + + elif line.get_id() == 199: + # Fish trap food amount (hardcoded in civ) + starting_amount = storage["amount"].value + starting_amount += dataset.genie_civs[1]["resources"][88].value + + else: + starting_amount = storage["amount"].value + + spot_raw_api_object.add_raw_member("starting_amount", + starting_amount, + "engine.util.resource_spot.ResourceSpot") + + # Max amount + spot_raw_api_object.add_raw_member("max_amount", + starting_amount, + "engine.util.resource_spot.ResourceSpot") + + # Decay rate + decay_rate = current_unit["resource_decay"].value + spot_raw_api_object.add_raw_member("decay_rate", + decay_rate, + "engine.util.resource_spot.ResourceSpot") + + spot_forward_ref = ForwardRef(line, spot_name) + ability_raw_api_object.add_raw_member("resources", + spot_forward_ref, + "engine.ability.type.Harvestable") + line.add_raw_api_object(spot_raw_api_object) + + # Only one resource spot per ability + break + + # Harvest Progress (we don't use this for Aoe2) + ability_raw_api_object.add_raw_member("harvest_progress", + [], + "engine.ability.type.Harvestable") + + # Restock Progress + progress_forward_refs = [] + if line.get_class_id() == 49: + # Farms + # ===================================================================================== + progress_ref = f"{ability_ref}.RestockProgress33" + progress_raw_api_object = RawAPIObject(progress_ref, + "RestockProgress33", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Restock"], + "engine.util.progress.Progress") + + # Interval = (0.0, 33.0) + progress_raw_api_object.add_raw_member("left_boundary", + 0.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 33.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction1" + terrain_group = dataset.terrain_groups[29] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + init_state_ref = f"{game_entity_name}.Constructable.InitState" + init_state_forward_ref = ForwardRef(line, init_state_ref) + property_raw_api_object.add_raw_member("state_change", + init_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.RestockProgress66" + progress_raw_api_object = RawAPIObject(progress_ref, + "RestockProgress66", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Restock"], + "engine.util.progress.Progress") + + # Interval = (33.0, 66.0) + progress_raw_api_object.add_raw_member("left_boundary", + 33.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 66.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction2" + terrain_group = dataset.terrain_groups[30] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + construct_state_ref = f"{game_entity_name}.Constructable.ConstructState" + construct_state_forward_ref = ForwardRef(line, construct_state_ref) + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.RestockProgress100" + progress_raw_api_object = RawAPIObject(progress_ref, + "RestockProgress100", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Restock"], + "engine.util.progress.Progress") + + progress_raw_api_object.add_raw_member("left_boundary", + 66.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 100.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction3" + terrain_group = dataset.terrain_groups[31] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + construct_state_ref = f"{game_entity_name}.Constructable.ConstructState" + construct_state_forward_ref = ForwardRef(line, construct_state_ref) + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ======================================================================= + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + + ability_raw_api_object.add_raw_member("restock_progress", + progress_forward_refs, + "engine.ability.type.Harvestable") + + # Gatherer limit (infinite in AoC except for farms) + gatherer_limit = MemberSpecialValue.NYAN_INF + if line.get_class_id() == 49: + gatherer_limit = 1 + + ability_raw_api_object.add_raw_member("gatherer_limit", + gatherer_limit, + "engine.ability.type.Harvestable") + + # Unit have to die before they are harvestable (except for farms) + harvestable_by_default = current_unit["hit_points"].value == 0 + if line.get_class_id() == 49: + harvestable_by_default = True + + ability_raw_api_object.add_raw_member("harvestable_by_default", + harvestable_by_default, + "engine.ability.type.Harvestable") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/herd.py b/openage/convert/processor/conversion/aoc/ability/herd.py new file mode 100644 index 0000000000..1f71f0c6ca --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/herd.py @@ -0,0 +1,94 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Herd ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def herd_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Herd 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}.Herd" + ability_raw_api_object = RawAPIObject(ability_ref, "Herd", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Herd") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Strength + ability_raw_api_object.add_raw_member("strength", + 0, + "engine.ability.type.Herd") + + # Allowed types + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Herdable"].get_nyan_object() + ] + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.Herd") + + # Blacklisted entities + ability_raw_api_object.add_raw_member("blacklisted_entities", + [], + "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 diff --git a/openage/convert/processor/conversion/aoc/ability/herdable.py b/openage/convert/processor/conversion/aoc/ability/herdable.py new file mode 100644 index 0000000000..80b28ebbb3 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/herdable.py @@ -0,0 +1,55 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Herd ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def herdable_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Herdable 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}.Herdable" + ability_raw_api_object = RawAPIObject(ability_ref, + "Herdable", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Herdable") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Mode + mode = dataset.nyan_api_objects["engine.util.herdable_mode.type.LongestTimeInRange"] + ability_raw_api_object.add_raw_member("mode", mode, "engine.ability.type.Herdable") + + # Discover range + ability_raw_api_object.add_raw_member("adjacent_discover_range", + 1.0, + "engine.ability.type.Herdable") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/idle.py b/openage/convert/processor/conversion/aoc/ability/idle.py new file mode 100644 index 0000000000..36ce28af41 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/idle.py @@ -0,0 +1,121 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Idle ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation, create_civ_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def idle_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Idle 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.Idle" + ability_raw_api_object = RawAPIObject(ability_ref, "Idle", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Idle") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Ability properties + properties = {} + + # Animation + ability_animation_id = current_unit["idle_graphic0"].value + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = create_animation(line, + ability_animation_id, + property_ref, + "Idle", + "idle_") + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", + animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Create custom civ graphics + handled_graphics_set_ids = set() + for civ_group in dataset.civ_groups.values(): + civ = civ_group.civ + civ_id = civ_group.get_id() + + # Only proceed if the civ stores the unit in the line + if current_unit_id not in civ["units"].value.keys(): + continue + + civ_animation_id = civ["units"][current_unit_id]["idle_graphic0"].value + + if civ_animation_id != ability_animation_id: + # Find the corresponding graphics set + for set_id, items in gset_lookup_dict.items(): + if civ_id in items[0]: + graphics_set_id = set_id + break + + else: + raise RuntimeError(f"No graphics set found for civ id {civ_id}") + + # Check if the object for the animation has been created before + obj_exists = graphics_set_id in handled_graphics_set_ids + if not obj_exists: + handled_graphics_set_ids.add(graphics_set_id) + + obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}Idle" + filename_prefix = f"idle_{gset_lookup_dict[graphics_set_id][2]}_" + create_civ_animation(line, + civ_group, + civ_animation_id, + property_ref, + obj_prefix, + filename_prefix, + obj_exists) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/line_of_sight.py b/openage/convert/processor/conversion/aoc/ability/line_of_sight.py new file mode 100644 index 0000000000..ffe589e28f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/line_of_sight.py @@ -0,0 +1,74 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the LineOfSight ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def line_of_sight_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the LineOfSight 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + 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}.LineOfSight" + ability_raw_api_object = RawAPIObject(ability_ref, "LineOfSight", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.LineOfSight") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Line of sight + line_of_sight = current_unit["line_of_sight"].value + ability_raw_api_object.add_raw_member("range", line_of_sight, + "engine.ability.type.LineOfSight") + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties = { + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + } + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/live.py b/openage/convert/processor/conversion/aoc/ability/live.py new file mode 100644 index 0000000000..6c72b17794 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/live.py @@ -0,0 +1,122 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the LineOfSight ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def live_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Live 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 = line.get_head_unit() + 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}.Live" + ability_raw_api_object = RawAPIObject(ability_ref, "Live", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Live") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + attributes_set = [] + + # Health + # ======================================================================================= + health_ref = f"{game_entity_name}.Live.Health" + health_raw_api_object = RawAPIObject(health_ref, "Health", dataset.nyan_api_objects) + health_raw_api_object.add_raw_parent("engine.util.attribute.AttributeSetting") + health_location = ForwardRef(line, ability_ref) + health_raw_api_object.set_location(health_location) + + attribute_value = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object( + ) + health_raw_api_object.add_raw_member("attribute", + attribute_value, + "engine.util.attribute.AttributeSetting") + + # Lowest HP can go + health_raw_api_object.add_raw_member("min_value", + 0, + "engine.util.attribute.AttributeSetting") + + # Max HP and starting HP + max_hp_value = current_unit["hit_points"].value + health_raw_api_object.add_raw_member("max_value", + max_hp_value, + "engine.util.attribute.AttributeSetting") + + starting_value = max_hp_value + if isinstance(line, GenieBuildingLineGroup): + # Buildings spawn with 1 HP + starting_value = 1 + + health_raw_api_object.add_raw_member("starting_value", + starting_value, + "engine.util.attribute.AttributeSetting") + + line.add_raw_api_object(health_raw_api_object) + + # ======================================================================================= + health_forward_ref = ForwardRef(line, health_raw_api_object.get_id()) + attributes_set.append(health_forward_ref) + + if current_unit_id == 125: + # Faith (only monk) + faith_ref = f"{game_entity_name}.Live.Faith" + faith_raw_api_object = RawAPIObject(faith_ref, "Faith", dataset.nyan_api_objects) + faith_raw_api_object.add_raw_parent("engine.util.attribute.AttributeSetting") + faith_location = ForwardRef(line, ability_ref) + faith_raw_api_object.set_location(faith_location) + + attribute_value = dataset.pregen_nyan_objects["util.attribute.types.Faith"].get_nyan_object( + ) + faith_raw_api_object.add_raw_member("attribute", attribute_value, + "engine.util.attribute.AttributeSetting") + + # Lowest faith can go + faith_raw_api_object.add_raw_member("min_value", + 0, + "engine.util.attribute.AttributeSetting") + + # Max faith and starting faith + faith_raw_api_object.add_raw_member("max_value", + 100, + "engine.util.attribute.AttributeSetting") + faith_raw_api_object.add_raw_member("starting_value", + 100, + "engine.util.attribute.AttributeSetting") + + line.add_raw_api_object(faith_raw_api_object) + + faith_forward_ref = ForwardRef(line, faith_ref) + attributes_set.append(faith_forward_ref) + + ability_raw_api_object.add_raw_member("attributes", attributes_set, + "engine.ability.type.Live") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/move.py b/openage/convert/processor/conversion/aoc/ability/move.py new file mode 100644 index 0000000000..3b3dd5ede8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/move.py @@ -0,0 +1,312 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Move ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation, create_civ_animation, create_sound + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def move_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Move 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.Move" + ability_raw_api_object = RawAPIObject(ability_ref, "Move", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Move") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Ability properties + properties = {} + + # Animation + ability_animation_id = current_unit["move_graphics"].value + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + + animation_obj_prefix = "Move" + animation_filename_prefix = "move_" + + animation_forward_ref = create_animation(line, + ability_animation_id, + property_ref, + animation_obj_prefix, + animation_filename_prefix) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", + animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Create custom civ graphics + handled_graphics_set_ids = set() + for civ_group in dataset.civ_groups.values(): + civ = civ_group.civ + civ_id = civ_group.get_id() + + # Only proceed if the civ stores the unit in the line + if current_unit_id not in civ["units"].value.keys(): + continue + + civ_animation_id = civ["units"][current_unit_id]["move_graphics"].value + + if civ_animation_id != ability_animation_id: + # Find the corresponding graphics set + graphics_set_id = -1 + for set_id, items in gset_lookup_dict.items(): + if civ_id in items[0]: + graphics_set_id = set_id + break + + # Check if the object for the animation has been created before + obj_exists = graphics_set_id in handled_graphics_set_ids + if not obj_exists: + handled_graphics_set_ids.add(graphics_set_id) + + obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}Move" + filename_prefix = f"move_{gset_lookup_dict[graphics_set_id][2]}_" + create_civ_animation(line, + civ_group, + civ_animation_id, + property_ref, + obj_prefix, + filename_prefix, + obj_exists) + + # Command Sound + ability_comm_sound_id = current_unit["command_sound_id"].value + if ability_comm_sound_id > -1: + property_ref = f"{ability_ref}.CommandSound" + property_raw_api_object = RawAPIObject(property_ref, + "CommandSound", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + sounds_set = [] + + sound_obj_prefix = "Move" + + sound_forward_ref = create_sound(line, + ability_comm_sound_id, + property_ref, + sound_obj_prefix, + "command_") + sounds_set.append(sound_forward_ref) + property_raw_api_object.add_raw_member("sounds", sounds_set, + "engine.ability.property.type.CommandSound") + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Speed + speed = current_unit["speed"].value + ability_raw_api_object.add_raw_member("speed", speed, "engine.ability.type.Move") + + # Standard move modes + move_modes = [ + dataset.nyan_api_objects["engine.util.move_mode.type.AttackMove"], + dataset.nyan_api_objects["engine.util.move_mode.type.Normal"], + dataset.nyan_api_objects["engine.util.move_mode.type.Patrol"] + ] + + # Follow + ability_ref = f"{game_entity_name}.Move.Follow" + follow_raw_api_object = RawAPIObject(ability_ref, "Follow", dataset.nyan_api_objects) + follow_raw_api_object.add_raw_parent("engine.util.move_mode.type.Follow") + follow_location = ForwardRef(line, f"{game_entity_name}.Move") + follow_raw_api_object.set_location(follow_location) + + follow_range = current_unit["line_of_sight"].value - 1 + follow_raw_api_object.add_raw_member("range", + follow_range, + "engine.util.move_mode.type.Follow") + + line.add_raw_api_object(follow_raw_api_object) + follow_forward_ref = ForwardRef(line, follow_raw_api_object.get_id()) + move_modes.append(follow_forward_ref) + + ability_raw_api_object.add_raw_member("modes", move_modes, "engine.ability.type.Move") + + # Path type + path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() + restrictions = current_unit["terrain_restriction"].value + if restrictions in (0x00, 0x0C, 0x0E, 0x17): + # air units + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + + elif restrictions in (0x03, 0x0D, 0x0F): + # ships + path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() + + ability_raw_api_object.add_raw_member("path_type", path_type, "engine.ability.type.Move") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref + + +def move_projectile_ability(line: GenieGameEntityGroup, position: int = -1) -> ForwardRef: + """ + Adds the Move ability to a projectile of the specified 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 + """ + dataset = line.data + api_objects = dataset.nyan_api_objects + + if position == 0: + current_unit_id = line.get_head_unit_id() + projectile_id = line.get_head_unit()["projectile_id0"].value + current_unit = dataset.genie_units[projectile_id] + + elif position == 1: + current_unit_id = line.get_head_unit_id() + projectile_id = line.get_head_unit()["projectile_id1"].value + current_unit = dataset.genie_units[projectile_id] + + else: + raise ValueError(f"Invalid projectile number: {position}") + + 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"Projectile{position}.Move" + ability_raw_api_object = RawAPIObject(ability_ref, "Move", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Move") + ability_location = ForwardRef(line, + f"{game_entity_name}.ShootProjectile.Projectile{position}") + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Ability properties + properties = {} + + # Animation + ability_animation_id = current_unit["move_graphics"].value + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_obj_prefix = "ProjectileFly" + animation_filename_prefix = "projectile_fly_" + + animation_forward_ref = create_animation(line, + ability_animation_id, + property_ref, + animation_obj_prefix, + animation_filename_prefix) + + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", + animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Speed + speed = current_unit["speed"].value + ability_raw_api_object.add_raw_member("speed", speed, "engine.ability.type.Move") + + # Move modes + move_modes = [ + dataset.nyan_api_objects["engine.util.move_mode.type.Normal"], + ] + ability_raw_api_object.add_raw_member("modes", move_modes, "engine.ability.type.Move") + + # Path type + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + ability_raw_api_object.add_raw_member("path_type", path_type, "engine.ability.type.Move") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/named.py b/openage/convert/processor/conversion/aoc/ability/named.py new file mode 100644 index 0000000000..71be7959db --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/named.py @@ -0,0 +1,108 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Named ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_language_strings + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def named_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Named 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 = line.get_head_unit() + 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}.Named" + ability_raw_api_object = RawAPIObject(ability_ref, "Named", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Named") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Name + name_ref = f"{game_entity_name}.Named.{game_entity_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{game_entity_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(line, ability_ref) + name_raw_api_object.set_location(name_location) + + name_string_id = current_unit["language_dll_name"].value + translations = create_language_strings(line, + name_string_id, + name_ref, + f"{game_entity_name}Name") + name_raw_api_object.add_raw_member("translations", + translations, + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(line, name_ref) + ability_raw_api_object.add_raw_member("name", name_forward_ref, "engine.ability.type.Named") + line.add_raw_api_object(name_raw_api_object) + + # Description + description_ref = f"{game_entity_name}.Named.{game_entity_name}Description" + description_raw_api_object = RawAPIObject(description_ref, + f"{game_entity_name}Description", + dataset.nyan_api_objects) + description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + description_location = ForwardRef(line, ability_ref) + description_raw_api_object.set_location(description_location) + + description_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile") + + description_forward_ref = ForwardRef(line, description_ref) + ability_raw_api_object.add_raw_member("description", + description_forward_ref, + "engine.ability.type.Named") + line.add_raw_api_object(description_raw_api_object) + + # Long description + long_description_ref = f"{game_entity_name}.Named.{game_entity_name}LongDescription" + long_description_raw_api_object = RawAPIObject(long_description_ref, + f"{game_entity_name}LongDescription", + dataset.nyan_api_objects) + long_description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + long_description_location = ForwardRef(line, ability_ref) + long_description_raw_api_object.set_location(long_description_location) + + long_description_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile") + + long_description_forward_ref = ForwardRef(line, long_description_ref) + ability_raw_api_object.add_raw_member("long_description", + long_description_forward_ref, + "engine.ability.type.Named") + line.add_raw_api_object(long_description_raw_api_object) + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/overlay_terrain.py b/openage/convert/processor/conversion/aoc/ability/overlay_terrain.py new file mode 100644 index 0000000000..264015e9fb --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/overlay_terrain.py @@ -0,0 +1,56 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the OverlayTerrain ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def overlay_terrain_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the OverlayTerrain to a line. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :returns: The forward references for the abilities. + :rtype: ...dataformat.forward_ref.ForwardRef + """ + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.OverlayTerrain" + ability_raw_api_object = RawAPIObject(ability_ref, + "OverlayTerrain", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.OverlayTerrain") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Terrain (Use foundation terrain) + terrain_id = current_unit["foundation_terrain_id"].value + terrain = dataset.terrain_groups[terrain_id] + terrain_forward_ref = ForwardRef(terrain, terrain_lookup_dict[terrain_id][1]) + ability_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.ability.type.OverlayTerrain") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/pathable.py b/openage/convert/processor/conversion/aoc/ability/pathable.py new file mode 100644 index 0000000000..8aba68125c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/pathable.py @@ -0,0 +1,62 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Pathable ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def pathable_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Pathable 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}.Pathable" + ability_raw_api_object = RawAPIObject(ability_ref, + "Pathable", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Pathable") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Hitbox + hitbox_ref = f"{game_entity_name}.Collision.{game_entity_name}Hitbox" + hitbox_forward_ref = ForwardRef(line, hitbox_ref) + ability_raw_api_object.add_raw_member("hitbox", + hitbox_forward_ref, + "engine.ability.type.Pathable") + + # Costs + path_costs = { + dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object(): 255, # impassable + dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object(): 255, # impassable + } + ability_raw_api_object.add_raw_member("path_costs", + path_costs, + "engine.ability.type.Pathable") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/production_queue.py b/openage/convert/processor/conversion/aoc/ability/production_queue.py new file mode 100644 index 0000000000..0a60ecca00 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/production_queue.py @@ -0,0 +1,75 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ProductionQueue ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def production_queue_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ProductionQueue 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}.ProductionQueue" + ability_raw_api_object = RawAPIObject(ability_ref, + "ProductionQueue", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.ProductionQueue") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Size + size = 14 + + ability_raw_api_object.add_raw_member("size", + size, + "engine.ability.type.ProductionQueue") + + # Production modes + modes = [] + + mode_name = f"{game_entity_name}.ProvideContingent.CreatablesMode" + mode_raw_api_object = RawAPIObject(mode_name, "CreatablesMode", dataset.nyan_api_objects) + mode_raw_api_object.add_raw_parent("engine.util.production_mode.type.Creatables") + mode_location = ForwardRef(line, ability_ref) + mode_raw_api_object.set_location(mode_location) + + # AoE2 allows all creatables in production queue + mode_raw_api_object.add_raw_member("exclude", + [], + "engine.util.production_mode.type.Creatables") + + mode_forward_ref = ForwardRef(line, mode_name) + modes.append(mode_forward_ref) + + ability_raw_api_object.add_raw_member("production_modes", + modes, + "engine.ability.type.ProductionQueue") + + line.add_raw_api_object(mode_raw_api_object) + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/projectile.py b/openage/convert/processor/conversion/aoc/ability/projectile.py new file mode 100644 index 0000000000..db16695657 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/projectile.py @@ -0,0 +1,128 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Projectile ability. +""" +from __future__ import annotations +import typing + +from math import degrees + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def projectile_ability(line: GenieGameEntityGroup, position: int = 0) -> ForwardRef: + """ + Adds a Projectile ability to projectiles in a line. Which projectile should + be added is determined by the 'position' argument. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param position: When 0, gives the first projectile its ability. When 1, the second... + :type position: int + :returns: The forward reference for the ability. + :rtype: ...dataformat.forward_ref.ForwardRef + """ + current_unit = line.get_head_unit() + 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] + + # First projectile is mandatory + obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{str(position)}" + ability_ref = f"{game_entity_name}.ShootProjectile.Projectile{position}.Projectile" + ability_raw_api_object = RawAPIObject(ability_ref, + "Projectile", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Projectile") + ability_location = ForwardRef(line, obj_ref) + ability_raw_api_object.set_location(ability_location) + + # Arc + if position == 0: + projectile_id = current_unit["projectile_id0"].value + + elif position == 1: + projectile_id = current_unit["projectile_id1"].value + + else: + raise ValueError(f"Invalid projectile position {position}") + + projectile = dataset.genie_units[projectile_id] + arc = degrees(projectile["projectile_arc"].value) + ability_raw_api_object.add_raw_member("arc", + arc, + "engine.ability.type.Projectile") + + # Accuracy + accuracy_name = (f"{game_entity_name}.ShootProjectile." + f"Projectile{position}.Projectile.Accuracy") + accuracy_raw_api_object = RawAPIObject(accuracy_name, + "Accuracy", + dataset.nyan_api_objects) + accuracy_raw_api_object.add_raw_parent("engine.util.accuracy.Accuracy") + accuracy_location = ForwardRef(line, ability_ref) + accuracy_raw_api_object.set_location(accuracy_location) + + accuracy_value = current_unit["accuracy"].value + accuracy_raw_api_object.add_raw_member("accuracy", + accuracy_value, + "engine.util.accuracy.Accuracy") + + accuracy_dispersion = current_unit["accuracy_dispersion"].value + accuracy_raw_api_object.add_raw_member("accuracy_dispersion", + accuracy_dispersion, + "engine.util.accuracy.Accuracy") + dropoff_type = dataset.nyan_api_objects["engine.util.dropoff_type.type.InverseLinear"] + accuracy_raw_api_object.add_raw_member("dispersion_dropoff", + dropoff_type, + "engine.util.accuracy.Accuracy") + + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() + ] + accuracy_raw_api_object.add_raw_member("target_types", + allowed_types, + "engine.util.accuracy.Accuracy") + accuracy_raw_api_object.add_raw_member("blacklisted_entities", + [], + "engine.util.accuracy.Accuracy") + + line.add_raw_api_object(accuracy_raw_api_object) + accuracy_forward_ref = ForwardRef(line, accuracy_name) + ability_raw_api_object.add_raw_member("accuracy", + [accuracy_forward_ref], + "engine.ability.type.Projectile") + + # Target mode + target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.CurrentPosition"] + ability_raw_api_object.add_raw_member("target_mode", + target_mode, + "engine.ability.type.Projectile") + + # Ingore types; buildings are ignored unless targeted + ignore_forward_refs = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object() + ] + ability_raw_api_object.add_raw_member("ignored_types", + ignore_forward_refs, + "engine.ability.type.Projectile") + ability_raw_api_object.add_raw_member("unignored_entities", + [], + "engine.ability.type.Projectile") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/provide_contingent.py b/openage/convert/processor/conversion/aoc/ability/provide_contingent.py new file mode 100644 index 0000000000..816d6ae392 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/provide_contingent.py @@ -0,0 +1,97 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ProvideContingent ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieStackBuildingGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + +# ASDF: remove type hints in docstring + + +def provide_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ProvideContingent 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 = line.get_head_unit() + if isinstance(line, GenieStackBuildingGroup): + current_unit = line.get_stack_unit() + + 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}.ProvideContingent" + + # Stores the pop space + resource_storage = current_unit["resource_storage"].value + + contingents = [] + for storage in resource_storage: + type_id = storage["type"].value + + if type_id == 4: + resource = dataset.pregen_nyan_objects["util.resource.types.PopulationSpace"].get_nyan_object( + ) + resource_name = "PopSpace" + + else: + continue + + amount = storage["amount"].value + + contingent_amount_name = f"{game_entity_name}.ProvideContingent.{resource_name}" + contingent_amount = RawAPIObject(contingent_amount_name, resource_name, + dataset.nyan_api_objects) + contingent_amount.add_raw_parent("engine.util.resource.ResourceAmount") + ability_forward_ref = ForwardRef(line, ability_ref) + contingent_amount.set_location(ability_forward_ref) + + contingent_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + contingent_amount.add_raw_member("amount", + amount, + "engine.util.resource.ResourceAmount") + + line.add_raw_api_object(contingent_amount) + contingent_amount_forward_ref = ForwardRef(line, + contingent_amount_name) + contingents.append(contingent_amount_forward_ref) + + if not contingents: + # Do not create the ability if the unit provides no contingents + return None + + ability_raw_api_object = RawAPIObject(ability_ref, + "ProvideContingent", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.ProvideContingent") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + ability_raw_api_object.add_raw_member("amount", + contingents, + "engine.ability.type.ProvideContingent") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/rally_point.py b/openage/convert/processor/conversion/aoc/ability/rally_point.py new file mode 100644 index 0000000000..49fe4f078e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/rally_point.py @@ -0,0 +1,44 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the RallyPoint ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def rally_point_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the RallyPoint 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}.RallyPoint" + ability_raw_api_object = RawAPIObject(ability_ref, "RallyPoint", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.RallyPoint") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/regenerate_attribute.py b/openage/convert/processor/conversion/aoc/ability/regenerate_attribute.py new file mode 100644 index 0000000000..d0c74ef95a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/regenerate_attribute.py @@ -0,0 +1,96 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the RegenerateAttribute ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def regenerate_attribute_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the RegenerateAttribute ability to a line. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :returns: The forward references for the ability. + :rtype: list + """ + current_unit_id = line.get_head_unit_id() + dataset = line.data + + attribute = None + attribute_name = "" + if current_unit_id == 125: + # Monk; regenerates Faith + attribute = dataset.pregen_nyan_objects["util.attribute.types.Faith"].get_nyan_object() + attribute_name = "Faith" + + elif current_unit_id == 692: + # Berserk: regenerates Health + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + attribute_name = "Health" + + else: + return [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_name = f"Regenerate{attribute_name}" + 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("engine.ability.type.RegenerateAttribute") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Attribute rate + # =============================================================================== + rate_name = f"{attribute_name}Rate" + rate_ref = f"{game_entity_name}.{ability_name}.{rate_name}" + rate_raw_api_object = RawAPIObject(rate_ref, rate_name, dataset.nyan_api_objects) + rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") + rate_location = ForwardRef(line, ability_ref) + rate_raw_api_object.set_location(rate_location) + + # Attribute + rate_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeRate") + + # Rate + attribute_rate = 0 + if current_unit_id == 125: + # stored in civ resources + attribute_rate = dataset.genie_civs[0]["resources"][35].value + + elif current_unit_id == 692: + # stored in civ resources, but has to get converted to amount/second + heal_timer = dataset.genie_civs[0]["resources"][96].value + attribute_rate = 1 / heal_timer + + rate_raw_api_object.add_raw_member("rate", + attribute_rate, + "engine.util.attribute.AttributeRate") + + line.add_raw_api_object(rate_raw_api_object) + # =============================================================================== + rate_forward_ref = ForwardRef(line, rate_ref) + ability_raw_api_object.add_raw_member("rate", + rate_forward_ref, + "engine.ability.type.RegenerateAttribute") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return [ability_forward_ref] diff --git a/openage/convert/processor/conversion/aoc/ability/regenerate_resource_spot.py b/openage/convert/processor/conversion/aoc/ability/regenerate_resource_spot.py new file mode 100644 index 0000000000..942707b0cd --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/regenerate_resource_spot.py @@ -0,0 +1,24 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the RegenerateResourceSpot ability. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def regenerate_resource_spot_ability(line: GenieGameEntityGroup) -> None: + """ + Adds the RegenerateResourceSpot 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 + """ + # Unused in AoC + return None diff --git a/openage/convert/processor/conversion/aoc/ability/remove_storage.py b/openage/convert/processor/conversion/aoc/ability/remove_storage.py new file mode 100644 index 0000000000..b4413dfe9e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/remove_storage.py @@ -0,0 +1,65 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the RemoveStorage ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def remove_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the RemoveStorage 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}.RemoveStorage" + ability_raw_api_object = RawAPIObject(ability_ref, + "RemoveStorage", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.RemoveStorage") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Container + container_ref = f"{game_entity_name}.Storage.{game_entity_name}Container" + container_forward_ref = ForwardRef(line, container_ref) + ability_raw_api_object.add_raw_member("container", + container_forward_ref, + "engine.ability.type.RemoveStorage") + + # Storage elements + elements = [] + entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) + for entity in line.garrison_entities: + entity_ref = entity_lookups[entity.get_head_unit_id()][0] + entity_forward_ref = ForwardRef(entity, entity_ref) + elements.append(entity_forward_ref) + + ability_raw_api_object.add_raw_member("storage_elements", + elements, + "engine.ability.type.RemoveStorage") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/research.py b/openage/convert/processor/conversion/aoc/ability/research.py new file mode 100644 index 0000000000..3dce8646d4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/research.py @@ -0,0 +1,91 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Research ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def research_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Research 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 + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + ability_ref = f"{game_entity_name}.Research" + ability_raw_api_object = RawAPIObject(ability_ref, + "Research", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Research") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties = { + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + } + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + researchables_set = [] + for researchable in line.researches: + if researchable.is_unique(): + # Skip this because unique techs are handled by civs + continue + + # ResearchableTech objects are created for each unit/building + # line individually to avoid duplicates. We just point to the + # raw API objects here. + researchable_id = researchable.get_id() + researchable_name = tech_lookup_dict[researchable_id][0] + + raw_api_object_ref = f"{researchable_name}.ResearchableTech" + researchable_forward_ref = ForwardRef(researchable, + raw_api_object_ref) + researchables_set.append(researchable_forward_ref) + + ability_raw_api_object.add_raw_member("researchables", researchables_set, + "engine.ability.type.Research") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/resistance.py b/openage/convert/processor/conversion/aoc/ability/resistance.py new file mode 100644 index 0000000000..aec32cd1fc --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/resistance.py @@ -0,0 +1,67 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Resistance ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup, GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..effect_subprocessor import AoCEffectSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def resistance_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Resistance 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}.Resistance" + ability_raw_api_object = RawAPIObject(ability_ref, + "Resistance", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Resistance") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Resistances + resistances = [] + resistances.extend(AoCEffectSubprocessor.get_attack_resistances(line, ability_ref)) + if isinstance(line, (GenieUnitLineGroup, GenieBuildingLineGroup)): + resistances.extend(AoCEffectSubprocessor.get_convert_resistances(line, ability_ref)) + + if isinstance(line, GenieUnitLineGroup) and not line.is_repairable(): + resistances.extend(AoCEffectSubprocessor.get_heal_resistances(line, ability_ref)) + + if isinstance(line, GenieBuildingLineGroup): + resistances.extend( + AoCEffectSubprocessor.get_construct_resistances(line, ability_ref)) + + if line.is_repairable(): + resistances.extend(AoCEffectSubprocessor.get_repair_resistances(line, ability_ref)) + + ability_raw_api_object.add_raw_member("resistances", + resistances, + "engine.ability.type.Resistance") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/resource_storage.py b/openage/convert/processor/conversion/aoc/ability/resource_storage.py new file mode 100644 index 0000000000..59abedd4f5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/resource_storage.py @@ -0,0 +1,269 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ResourceStorage ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ResourceStorage 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 + """ + if isinstance(line, GenieVillagerGroup): + gatherers = line.variants[0].line + + else: + gatherers = [line.line[0]] + + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.ResourceStorage" + ability_raw_api_object = RawAPIObject(ability_ref, + "ResourceStorage", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.ResourceStorage") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Create containers + containers = [] + for gatherer in gatherers: + unit_commands = gatherer["unit_commands"].value + resource = None + + used_command = None + for command in unit_commands: + # Find a gather ability. It doesn't matter which one because + # they should all produce the same resource for one genie unit. + type_id = command["type"].value + + if type_id not in (5, 110, 111): + continue + + resource_id = command["resource_out"].value + + # If resource_out is not specified, the gatherer harvests resource_in + if resource_id == -1: + resource_id = command["resource_in"].value + + if resource_id == 0: + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object( + ) + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object( + ) + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( + ) + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object( + ) + + elif type_id == 111: + target_id = command["unit_id"].value + if target_id not in dataset.building_lines.keys(): + # Skips the trade workshop trading which is never used + continue + + # Trade goods --> gold + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object( + ) + + else: + continue + + used_command = command + + if not used_command: + # 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: + # Skips hunting wolves + continue + + container_name = f"{gather_lookup_dict[gatherer_unit_id][0]}Container" + + elif used_command["type"].value == 111: + # Trading + container_name = "TradeContainer" + + container_ref = f"{ability_ref}.{container_name}" + container_raw_api_object = RawAPIObject(container_ref, + container_name, + dataset.nyan_api_objects) + container_raw_api_object.add_raw_parent("engine.util.storage.ResourceContainer") + container_location = ForwardRef(line, ability_ref) + container_raw_api_object.set_location(container_location) + + # Resource + container_raw_api_object.add_raw_member("resource", + resource, + "engine.util.storage.ResourceContainer") + + # Carry capacity + carry_capacity = None + if line.is_gatherer(): + carry_capacity = gatherer["resource_capacity"].value + + elif used_command["type"].value == 111: + # No restriction for trading + carry_capacity = MemberSpecialValue.NYAN_INF + + container_raw_api_object.add_raw_member("max_amount", + carry_capacity, + "engine.util.storage.ResourceContainer") + + # Carry progress + carry_progress = [] + carry_move_animation_id = used_command["carry_sprite_id"].value + if carry_move_animation_id > -1: + # ================================================================================= + progress_ref = f"{ability_ref}.{container_name}CarryProgress" + progress_raw_api_object = RawAPIObject(progress_ref, + f"{container_name}CarryProgress", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, container_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Carry"], + "engine.util.progress.Progress") + + # Interval = (20.0, 100.0) + progress_raw_api_object.add_raw_member("left_boundary", + 20.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 100.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ================================================================================= + # Animated property (animation overrides) + # ================================================================================= + property_ref = f"{progress_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.Animated") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + # ================================================================================= + overrides = [] + # ================================================================================= + # Move override + # ================================================================================= + override_ref = f"{property_ref}.MoveOverride" + override_raw_api_object = RawAPIObject(override_ref, + "MoveOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, property_ref) + override_raw_api_object.set_location(override_location) + + line.add_raw_api_object(override_raw_api_object) + + move_forward_ref = ForwardRef(line, f"{game_entity_name}.Move") + override_raw_api_object.add_raw_member("ability", + move_forward_ref, + "engine.util.animation_override.AnimationOverride") + + # Animation + animations_set = [] + animation_forward_ref = create_animation(line, + carry_move_animation_id, + override_ref, + "Move", + "move_carry_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member("animations", + animations_set, + "engine.util.animation_override.AnimationOverride") + + override_raw_api_object.add_raw_member("priority", + 1, + "engine.util.animation_override.AnimationOverride") + + override_forward_ref = ForwardRef(line, override_ref) + overrides.append(override_forward_ref) + # ================================================================================= + # TODO: Idle override (stops on last used frame of Move override?) + # ================================================================================= + # ================================================================================= + property_raw_api_object.add_raw_member("overrides", + overrides, + "engine.util.progress.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + + properties.update({ + api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref + }) + # ================================================================================= + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_ref = ForwardRef(line, progress_ref) + carry_progress.append(progress_forward_ref) + + container_raw_api_object.add_raw_member("carry_progress", + carry_progress, + "engine.util.storage.ResourceContainer") + + line.add_raw_api_object(container_raw_api_object) + + container_forward_ref = ForwardRef(line, container_ref) + containers.append(container_forward_ref) + + ability_raw_api_object.add_raw_member("containers", + containers, + "engine.ability.type.ResourceStorage") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/restock.py b/openage/convert/processor/conversion/aoc/ability/restock.py new file mode 100644 index 0000000000..9093f4bb89 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/restock.py @@ -0,0 +1,155 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Restock ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def restock_ability(line: GenieGameEntityGroup, restock_target_id: int) -> ForwardRef: + """ + Adds the Restock 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 + api_objects = dataset.nyan_api_objects + + # get the restock target + converter_groups = {} + converter_groups.update(dataset.unit_lines) + converter_groups.update(dataset.building_lines) + converter_groups.update(dataset.ambient_groups) + + restock_target = converter_groups[restock_target_id] + + if not restock_target.is_harvestable(): + raise RuntimeError(f"{restock_target} cannot be restocked: is not harvestable") + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + restock_lookup_dict = internal_name_lookups.get_restock_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + ability_ref = f"{game_entity_name}.{restock_lookup_dict[restock_target_id][0]}" + ability_raw_api_object = RawAPIObject(ability_ref, + restock_lookup_dict[restock_target_id][0], + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Restock") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Ability properties + properties = {} + + ability_animation_id = -1 + if isinstance(line, GenieVillagerGroup) and restock_target_id == 50: + # Search for the build graphic of farms + restock_unit = line.get_units_with_command(101)[0] + commands = restock_unit["unit_commands"].value + for command in commands: + type_id = command["type"].value + + if type_id == 101: + ability_animation_id = command["work_sprite_id"].value + + if ability_animation_id > -1: + # Make the ability animated + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = create_animation( + line, + ability_animation_id, + property_ref, + restock_lookup_dict[restock_target_id][0], + f"{restock_lookup_dict[restock_target_id][1]}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", + animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Auto restock + ability_raw_api_object.add_raw_member("auto_restock", + True, # always True since AoC + "engine.ability.type.Restock") + + # Target + restock_target_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + restock_target_name = restock_target_lookup_dict[restock_target_id][0] + spot_forward_ref = ForwardRef(restock_target, + (f"{restock_target_name}.Harvestable." + f"{restock_target_name}ResourceSpot")) + ability_raw_api_object.add_raw_member("target", + spot_forward_ref, + "engine.ability.type.Restock") + + # restock time + restock_time = restock_target.get_head_unit()["creation_time"].value + ability_raw_api_object.add_raw_member("restock_time", + restock_time, + "engine.ability.type.Restock") + + # Manual/Auto Cost + # Link to the same Cost object as Create + cost_forward_ref = ForwardRef(restock_target, + (f"{restock_target_name}.CreatableGameEntity." + f"{restock_target_name}Cost")) + ability_raw_api_object.add_raw_member("manual_cost", + cost_forward_ref, + "engine.ability.type.Restock") + ability_raw_api_object.add_raw_member("auto_cost", + cost_forward_ref, + "engine.ability.type.Restock") + + # Amount + restock_amount = restock_target.get_head_unit()["resource_capacity"].value + if restock_target_id == 50: + # Farm food amount (hardcoded in civ) + restock_amount = dataset.genie_civs[1]["resources"][36].value + + elif restock_target_id == 199: + # Fish trap added food amount (hardcoded in civ) + restock_amount += dataset.genie_civs[1]["resources"][88].value + + ability_raw_api_object.add_raw_member("amount", + restock_amount, + "engine.ability.type.Restock") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/selectable.py b/openage/convert/processor/conversion/aoc/ability/selectable.py new file mode 100644 index 0000000000..77113ad9f2 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/selectable.py @@ -0,0 +1,238 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Selectable ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_sound + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def selectable_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds Selectable abilities to a line. Units will get two of these, + one Rectangle box for the Self stance and one MatchToSprite box + for other stances. + + :param line: Unit/Building line that gets the abilities. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :returns: The forward reference for the abilities. + :rtype: ...dataformat.forward_ref.ForwardRef + """ + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_refs = (f"{game_entity_name}.Selectable",) + ability_names = ("Selectable",) + + if isinstance(line, GenieUnitLineGroup): + ability_refs = (f"{game_entity_name}.SelectableOthers", + f"{game_entity_name}.SelectableSelf") + ability_names = ("SelectableOthers", + "SelectableSelf") + + abilities = [] + + # First box (MatchToSprite) + ability_ref = ability_refs[0] + ability_name = ability_names[0] + + ability_raw_api_object = RawAPIObject(ability_ref, ability_name, dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Selectable") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Selection box + box_ref = dataset.nyan_api_objects["engine.util.selection_box.type.MatchToSprite"] + ability_raw_api_object.add_raw_member("selection_box", + box_ref, + "engine.ability.type.Selectable") + + # Ability properties + properties = {} + + # Diplomacy setting (for units) + if isinstance(line, GenieUnitLineGroup): + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + stances = [ + dataset.pregen_nyan_objects["util.diplomatic_stance.types.Enemy"].get_nyan_object(), + dataset.pregen_nyan_objects["util.diplomatic_stance.types.Neutral"].get_nyan_object( + ), + dataset.pregen_nyan_objects["util.diplomatic_stance.types.Friendly"].get_nyan_object( + ) + ] + property_raw_api_object.add_raw_member("stances", + stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + else: + ability_comm_sound_id = current_unit["selection_sound_id"].value + if ability_comm_sound_id > -1: + property_ref = f"{ability_ref}.CommandSound" + property_raw_api_object = RawAPIObject(property_ref, + "CommandSound", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + sounds_set = [] + sound_forward_ref = create_sound(line, + ability_comm_sound_id, + property_ref, + ability_name, + "command_") + sounds_set.append(sound_forward_ref) + property_raw_api_object.add_raw_member("sounds", + sounds_set, + "engine.ability.property.type.CommandSound") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref + }) + 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()) + + abilities.append(ability_forward_ref) + + if not isinstance(line, GenieUnitLineGroup): + return abilities + + # Second box (Rectangle) + ability_ref = ability_refs[1] + ability_name = ability_names[1] + + ability_raw_api_object = RawAPIObject(ability_ref, + ability_name, + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Selectable") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Ability properties + properties = {} + + # Command Sound + ability_comm_sound_id = current_unit["selection_sound_id"].value + if ability_comm_sound_id > -1: + property_ref = f"{ability_ref}.CommandSound" + property_raw_api_object = RawAPIObject(property_ref, + "CommandSound", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + sounds_set = [] + sound_forward_ref = create_sound(line, + ability_comm_sound_id, + property_ref, + ability_name, + "command_") + sounds_set.append(sound_forward_ref) + property_raw_api_object.add_raw_member("sounds", + sounds_set, + "engine.ability.property.type.CommandSound") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Selection box + box_name = f"{game_entity_name}.SelectableSelf.Rectangle" + box_raw_api_object = RawAPIObject(box_name, "Rectangle", dataset.nyan_api_objects) + box_raw_api_object.add_raw_parent("engine.util.selection_box.type.Rectangle") + box_location = ForwardRef(line, ability_ref) + box_raw_api_object.set_location(box_location) + + width = current_unit["selection_shape_x"].value + box_raw_api_object.add_raw_member("width", + width, + "engine.util.selection_box.type.Rectangle") + + height = current_unit["selection_shape_y"].value + box_raw_api_object.add_raw_member("height", + height, + "engine.util.selection_box.type.Rectangle") + + line.add_raw_api_object(box_raw_api_object) + + box_forward_ref = ForwardRef(line, box_name) + ability_raw_api_object.add_raw_member("selection_box", + box_forward_ref, + "engine.ability.type.Selectable") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + abilities.append(ability_forward_ref) + + return abilities diff --git a/openage/convert/processor/conversion/aoc/ability/send_back_to_task.py b/openage/convert/processor/conversion/aoc/ability/send_back_to_task.py new file mode 100644 index 0000000000..a7f4686ef1 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/send_back_to_task.py @@ -0,0 +1,56 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the SendBackToTask ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def send_back_to_task_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the SendBackToTask 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}.SendBackToTask" + ability_raw_api_object = RawAPIObject(ability_ref, + "SendBackToTask", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.SendBackToTask") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Only works on villagers + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Villager"].get_nyan_object() + ] + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.SendBackToTask") + ability_raw_api_object.add_raw_member("blacklisted_entities", + [], + "engine.ability.type.SendBackToTask") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/shoot_projectile.py b/openage/convert/processor/conversion/aoc/ability/shoot_projectile.py new file mode 100644 index 0000000000..63ad9f0e61 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/shoot_projectile.py @@ -0,0 +1,290 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ShootProjectile ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation, create_sound + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> ForwardRef: + """ + Adds the ShootProjectile 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + + ability_name = command_lookup_dict[command_id][0] + + game_entity_name = name_lookup_dict[current_unit_id][0] + 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("engine.ability.type.ShootProjectile") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # 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: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = create_animation( + line, + ability_animation_id, + property_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", + animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Command Sound + ability_comm_sound_id = current_unit["command_sound_id"].value + if ability_comm_sound_id > -1: + property_ref = f"{ability_ref}.CommandSound" + property_raw_api_object = RawAPIObject(property_ref, + "CommandSound", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + sounds_set = [] + sound_forward_ref = create_sound(line, + ability_comm_sound_id, + property_ref, + ability_name, + "command_") + sounds_set.append(sound_forward_ref) + property_raw_api_object.add_raw_member("sounds", + sounds_set, + "engine.ability.property.type.CommandSound") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Projectile + projectiles = [] + projectile_primary = current_unit["projectile_id0"].value + if projectile_primary > -1: + projectiles.append(ForwardRef(line, + f"{game_entity_name}.ShootProjectile.Projectile0")) + + projectile_secondary = current_unit["projectile_id1"].value + if projectile_secondary > -1: + projectiles.append(ForwardRef(line, + f"{game_entity_name}.ShootProjectile.Projectile1")) + + ability_raw_api_object.add_raw_member("projectiles", + projectiles, + "engine.ability.type.ShootProjectile") + + # Projectile count + min_projectiles = current_unit["projectile_min_count"].value + max_projectiles = current_unit["projectile_max_count"].value + + if projectile_primary == -1: + # Special case where only the second projectile is defined (town center) + # The min/max projectile count is lowered by 1 in this case + min_projectiles -= 1 + max_projectiles -= 1 + + elif min_projectiles == 0 and max_projectiles == 0: + # If there's a primary projectile defined, but these values are 0, + # the game still fires a projectile on attack. + min_projectiles += 1 + max_projectiles += 1 + + if current_unit_id == 236: + # Bombard Tower (gets treated like a tower for max projectiles) + max_projectiles = 5 + + ability_raw_api_object.add_raw_member("min_projectiles", + min_projectiles, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("max_projectiles", + max_projectiles, + "engine.ability.type.ShootProjectile") + + # Reload time and delay + reload_time = current_unit["attack_speed"].value + ability_raw_api_object.add_raw_member("reload_time", + reload_time, + "engine.ability.type.ShootProjectile") + + if ability_animation_id > -1: + animation = dataset.genie_graphics[ability_animation_id] + frame_rate = animation.get_frame_rate() + + else: + frame_rate = 0 + + spawn_delay_frames = current_unit["frame_delay"].value + spawn_delay = frame_rate * spawn_delay_frames + ability_raw_api_object.add_raw_member("spawn_delay", + spawn_delay, + "engine.ability.type.ShootProjectile") + + # TODO: Hardcoded? + ability_raw_api_object.add_raw_member("projectile_delay", + 0.1, + "engine.ability.type.ShootProjectile") + + # Turning + if isinstance(line, GenieBuildingLineGroup): + require_turning = False + + else: + require_turning = True + + ability_raw_api_object.add_raw_member("require_turning", + require_turning, + "engine.ability.type.ShootProjectile") + + # Manual Aiming (Mangonel + Trebuchet) + manual_aiming_allowed = line.get_head_unit_id() in (280, 331) + + ability_raw_api_object.add_raw_member("manual_aiming_allowed", + manual_aiming_allowed, + "engine.ability.type.ShootProjectile") + + # Spawning area + spawning_area_offset_x = current_unit["weapon_offset"][0].value + spawning_area_offset_y = current_unit["weapon_offset"][1].value + spawning_area_offset_z = current_unit["weapon_offset"][2].value + + ability_raw_api_object.add_raw_member("spawning_area_offset_x", + spawning_area_offset_x, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("spawning_area_offset_y", + spawning_area_offset_y, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("spawning_area_offset_z", + spawning_area_offset_z, + "engine.ability.type.ShootProjectile") + + spawning_area_width = current_unit["projectile_spawning_area_width"].value + spawning_area_height = current_unit["projectile_spawning_area_length"].value + spawning_area_randomness = current_unit["projectile_spawning_area_randomness"].value + + ability_raw_api_object.add_raw_member("spawning_area_width", + spawning_area_width, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("spawning_area_height", + spawning_area_height, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("spawning_area_randomness", + spawning_area_randomness, + "engine.ability.type.ShootProjectile") + + # Restrictions on targets (only units and buildings allowed) + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() + ] + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("blacklisted_entities", + [], + "engine.ability.type.ShootProjectile") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/stop.py b/openage/convert/processor/conversion/aoc/ability/stop.py new file mode 100644 index 0000000000..bd3119db48 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/stop.py @@ -0,0 +1,69 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Stop ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def stop_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Stop 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 + api_objects = dataset.nyan_api_objects + + 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}.Stop" + ability_raw_api_object = RawAPIObject(ability_ref, "Stop", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Stop") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + + # Ability properties + properties = { + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + } + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/storage.py b/openage/convert/processor/conversion/aoc/ability/storage.py new file mode 100644 index 0000000000..185b0517ac --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/storage.py @@ -0,0 +1,454 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Storage ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode, GenieMonkGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import create_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def storage_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Storage 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + 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}.Storage" + ability_raw_api_object = RawAPIObject(ability_ref, "Storage", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Storage") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Container + # ============================================================================== + container_name = f"{game_entity_name}.Storage.{game_entity_name}Container" + container_raw_api_object = RawAPIObject(container_name, + f"{game_entity_name}Container", + dataset.nyan_api_objects) + container_raw_api_object.add_raw_parent("engine.util.storage.EntityContainer") + container_location = ForwardRef(line, ability_ref) + container_raw_api_object.set_location(container_location) + + garrison_mode = line.get_garrison_mode() + + # Allowed types + # TODO: Any should be fine for now, since Enter/Exit abilities limit the stored elements + allowed_types = [dataset.nyan_api_objects["engine.util.game_entity_type.type.Any"]] + + container_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.util.storage.EntityContainer") + + # Blacklisted entities + container_raw_api_object.add_raw_member("blacklisted_entities", + [], + "engine.util.storage.EntityContainer") + + # Define storage elements + storage_element_defs = [] + if garrison_mode is GenieGarrisonMode.UNIT_GARRISON: + for storage_element in line.garrison_entities: + storage_element_name = name_lookup_dict[storage_element.get_head_unit_id()][0] + storage_def_ref = (f"{game_entity_name}.Storage." + f"{game_entity_name}Container." + f"{storage_element_name}StorageDef") + storage_def_raw_api_object = RawAPIObject(storage_def_ref, + f"{storage_element_name}StorageDef", + dataset.nyan_api_objects) + storage_def_raw_api_object.add_raw_parent( + "engine.util.storage.StorageElementDefinition") + storage_def_location = ForwardRef(line, container_name) + storage_def_raw_api_object.set_location(storage_def_location) + + # Storage element + storage_element_forward_ref = ForwardRef(storage_element, storage_element_name) + storage_def_raw_api_object.add_raw_member("storage_element", + storage_element_forward_ref, + "engine.util.storage.StorageElementDefinition") + + # Elements per slot + storage_def_raw_api_object.add_raw_member("elements_per_slot", + 1, + "engine.util.storage.StorageElementDefinition") + + # Conflicts + storage_def_raw_api_object.add_raw_member("conflicts", + [], + "engine.util.storage.StorageElementDefinition") + + # TODO: State change (optional) -> speed boost + + storage_def_forward_ref = ForwardRef(line, storage_def_ref) + storage_element_defs.append(storage_def_forward_ref) + line.add_raw_api_object(storage_def_raw_api_object) + + container_raw_api_object.add_raw_member("storage_element_defs", + storage_element_defs, + "engine.util.storage.EntityContainer") + + # Container slots + slots = current_unit["garrison_capacity"].value + if garrison_mode is GenieGarrisonMode.MONK: + slots = 1 + + container_raw_api_object.add_raw_member("slots", + slots, + "engine.util.storage.EntityContainer") + + # Carry progress + carry_progress = [] + if garrison_mode is GenieGarrisonMode.MONK and isinstance(line, GenieMonkGroup): + switch_unit = line.get_switch_unit() + carry_idle_animation_id = switch_unit["idle_graphic0"].value + carry_move_animation_id = switch_unit["move_graphics"].value + + progress_ref = f"{ability_ref}.CarryProgress" + progress_raw_api_object = RawAPIObject(progress_ref, + "CarryProgress", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Carry"], + "engine.util.progress.Progress") + + # Interval = (0.0, 100.0) + progress_raw_api_object.add_raw_member("left_boundary", + 0.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 100.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Animated property (animation overrides) + # ===================================================================================== + property_ref = f"{progress_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.Animated") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + # ===================================================================================== + overrides = [] + # Idle override + # ===================================================================================== + override_ref = f"{property_ref}.IdleOverride" + override_raw_api_object = RawAPIObject(override_ref, + "IdleOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, property_ref) + override_raw_api_object.set_location(override_location) + + idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") + override_raw_api_object.add_raw_member("ability", + idle_forward_ref, + "engine.util.animation_override.AnimationOverride") + + # Animation + animations_set = [] + animation_forward_ref = create_animation(line, + carry_idle_animation_id, + override_ref, + "Idle", + "idle_carry_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member("animations", + animations_set, + "engine.util.animation_override.AnimationOverride") + + override_raw_api_object.add_raw_member("priority", + 1, + "engine.util.animation_override.AnimationOverride") + + override_forward_ref = ForwardRef(line, override_ref) + overrides.append(override_forward_ref) + line.add_raw_api_object(override_raw_api_object) + # ===================================================================================== + # Move override + # ===================================================================================== + override_ref = f"{property_ref}.MoveOverride" + override_raw_api_object = RawAPIObject(override_ref, + "MoveOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, property_ref) + override_raw_api_object.set_location(override_location) + + idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Move") + override_raw_api_object.add_raw_member("ability", + idle_forward_ref, + "engine.util.animation_override.AnimationOverride") + + # Animation + animations_set = [] + animation_forward_ref = create_animation(line, + carry_move_animation_id, + override_ref, + "Move", + "move_carry_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member("animations", + animations_set, + "engine.util.animation_override.AnimationOverride") + + override_raw_api_object.add_raw_member("priority", + 1, + "engine.util.animation_override.AnimationOverride") + + override_forward_ref = ForwardRef(line, override_ref) + overrides.append(override_forward_ref) + line.add_raw_api_object(override_raw_api_object) + # ===================================================================================== + property_raw_api_object.add_raw_member("overrides", + overrides, + "engine.util.progress.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref + }) + + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + # ===================================================================================== + carry_state_name = f"{property_ref}.CarryRelicState" + carry_state_raw_api_object = RawAPIObject(carry_state_name, + "CarryRelicState", + dataset.nyan_api_objects) + carry_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") + carry_state_location = ForwardRef(line, property_ref) + carry_state_raw_api_object.set_location(carry_state_location) + + # Priority + carry_state_raw_api_object.add_raw_member("priority", + 1, + "engine.util.state_machine.StateChanger") + + # Enabled abilities + carry_state_raw_api_object.add_raw_member("enable_abilities", + [], + "engine.util.state_machine.StateChanger") + + # Disabled abilities + disabled_forward_refs = [] + + if line.has_command(104): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Convert")) + + if line.has_command(105): + disabled_forward_refs.append(ForwardRef(line, + f"{game_entity_name}.Heal")) + + carry_state_raw_api_object.add_raw_member("disable_abilities", + disabled_forward_refs, + "engine.util.state_machine.StateChanger") + + # Enabled modifiers + carry_state_raw_api_object.add_raw_member("enable_modifiers", + [], + "engine.util.state_machine.StateChanger") + + # Disabled modifiers + carry_state_raw_api_object.add_raw_member("disable_modifiers", + [], + "engine.util.state_machine.StateChanger") + + line.add_raw_api_object(carry_state_raw_api_object) + # ===================================================================================== + init_state_forward_ref = ForwardRef(line, carry_state_name) + property_raw_api_object.add_raw_member("state_change", + init_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_ref = ForwardRef(line, progress_ref) + carry_progress.append(progress_forward_ref) + + else: + # Garrison graphics + if current_unit.has_member("garrison_graphic"): + garrison_animation_id = current_unit["garrison_graphic"].value + + else: + garrison_animation_id = -1 + + if garrison_animation_id > -1: + progress_ref = f"{ability_ref}.CarryProgress" + progress_raw_api_object = RawAPIObject(progress_ref, + "CarryProgress", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.Carry"], + "engine.util.progress.Progress") + + # Interval = (0.0, 100.0) + progress_raw_api_object.add_raw_member("left_boundary", + 0.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 100.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # Animated property (animation overrides) + # ================================================================================= + property_ref = f"{progress_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.Animated") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + # ================================================================================= + override_ref = f"{property_ref}.IdleOverride" + override_raw_api_object = RawAPIObject(override_ref, + "IdleOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, property_ref) + override_raw_api_object.set_location(override_location) + + idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") + override_raw_api_object.add_raw_member("ability", + idle_forward_ref, + "engine.util.animation_override.AnimationOverride") + + # Animation + animations_set = [] + animation_forward_ref = create_animation(line, + garrison_animation_id, + override_ref, + "Idle", + "idle_garrison_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member("animations", + animations_set, + "engine.util.animation_override.AnimationOverride") + + override_raw_api_object.add_raw_member("priority", + 1, + "engine.util.animation_override.AnimationOverride") + + line.add_raw_api_object(override_raw_api_object) + # ================================================================================= + override_forward_ref = ForwardRef(line, override_ref) + property_raw_api_object.add_raw_member("overrides", + [override_forward_ref], + "engine.util.progress.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + + properties.update({ + api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_ref = ForwardRef(line, progress_ref) + carry_progress.append(progress_forward_ref) + line.add_raw_api_object(progress_raw_api_object) + + container_raw_api_object.add_raw_member("carry_progress", + carry_progress, + "engine.util.storage.EntityContainer") + + line.add_raw_api_object(container_raw_api_object) + # ============================================================================== + container_forward_ref = ForwardRef(line, container_name) + ability_raw_api_object.add_raw_member("container", + container_forward_ref, + "engine.ability.type.Storage") + + # Empty condition + if garrison_mode in (GenieGarrisonMode.UNIT_GARRISON, GenieGarrisonMode.MONK): + # Empty before death + condition = [ + dataset.pregen_nyan_objects["util.logic.literal.death.StandardHealthDeathLiteral"].get_nyan_object()] + + elif garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): + # Empty when HP < 20% + condition = [ + dataset.pregen_nyan_objects["util.logic.literal.garrison.BuildingDamageEmpty"].get_nyan_object()] + + else: + # Never empty automatically (transport ships) + condition = [] + + ability_raw_api_object.add_raw_member("empty_condition", + condition, + "engine.ability.type.Storage") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/terrain_requirement.py b/openage/convert/processor/conversion/aoc/ability/terrain_requirement.py new file mode 100644 index 0000000000..4797f6c38d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/terrain_requirement.py @@ -0,0 +1,68 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the TerrainRequirement ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def terrain_requirement_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the TerrainRequirement to a line. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :returns: The forward references for the abilities. + :rtype: ...dataformat.forward_ref.ForwardRef + """ + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( + dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.TerrainRequirement" + ability_raw_api_object = RawAPIObject(ability_ref, + "TerrainRequirement", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.TerrainRequirement") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Allowed types + allowed_types = [] + terrain_restriction = current_unit["terrain_restriction"].value + for terrain_type in terrain_type_lookup_dict.values(): + # Check if terrain type is covered by terrain restriction + if terrain_restriction in terrain_type[1]: + type_name = f"util.terrain_type.types.{terrain_type[2]}" + type_obj = dataset.pregen_nyan_objects[type_name].get_nyan_object() + allowed_types.append(type_obj) + + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.TerrainRequirement") + + # Blacklisted terrains + ability_raw_api_object.add_raw_member("blacklisted_terrains", + [], + "engine.ability.type.TerrainRequirement") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/trade.py b/openage/convert/processor/conversion/aoc/ability/trade.py new file mode 100644 index 0000000000..d6bd12ebd5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/trade.py @@ -0,0 +1,79 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Trade ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def trade_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Trade 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 = line.get_head_unit() + 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}.Trade" + ability_raw_api_object = RawAPIObject(ability_ref, "Trade", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Trade") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Trade route (use the trade route to the market) + trade_routes = [] + + unit_commands = current_unit["unit_commands"].value + for command in unit_commands: + # Find the trade command and the trade post id + type_id = command["type"].value + + if type_id != 111: + continue + + trade_post_id = command["unit_id"].value + if trade_post_id not in dataset.building_lines.keys(): + # Skips trade workshop + continue + + trade_post_line = dataset.building_lines[trade_post_id] + trade_post_name = name_lookup_dict[trade_post_id][0] + + trade_route_ref = f"{trade_post_name}.TradePost.AoE2{trade_post_name}TradeRoute" + trade_route_forward_ref = ForwardRef(trade_post_line, trade_route_ref) + trade_routes.append(trade_route_forward_ref) + + ability_raw_api_object.add_raw_member("trade_routes", + trade_routes, + "engine.ability.type.Trade") + + # container + container_forward_ref = ForwardRef( + line, f"{game_entity_name}.ResourceStorage.TradeContainer") + ability_raw_api_object.add_raw_member("container", + container_forward_ref, + "engine.ability.type.Trade") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/trade_post.py b/openage/convert/processor/conversion/aoc/ability/trade_post.py new file mode 100644 index 0000000000..418120e4e6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/trade_post.py @@ -0,0 +1,82 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the TradePost ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def trade_post_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the TradePost 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}.TradePost" + ability_raw_api_object = RawAPIObject(ability_ref, + "TradePost", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.TradePost") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Trade route + trade_routes = [] + # ===================================================================================== + trade_route_name = f"AoE2{game_entity_name}TradeRoute" + trade_route_ref = f"{game_entity_name}.TradePost.{trade_route_name}" + trade_route_raw_api_object = RawAPIObject(trade_route_ref, + trade_route_name, + dataset.nyan_api_objects) + trade_route_raw_api_object.add_raw_parent("engine.util.trade_route.type.AoE2TradeRoute") + trade_route_location = ForwardRef(line, ability_ref) + trade_route_raw_api_object.set_location(trade_route_location) + + # Trade resource + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() + trade_route_raw_api_object.add_raw_member("trade_resource", + resource, + "engine.util.trade_route.TradeRoute") + + # Start- and endpoints + market_forward_ref = ForwardRef(line, game_entity_name) + trade_route_raw_api_object.add_raw_member("start_trade_post", + market_forward_ref, + "engine.util.trade_route.TradeRoute") + trade_route_raw_api_object.add_raw_member("end_trade_post", + market_forward_ref, + "engine.util.trade_route.TradeRoute") + + trade_route_forward_ref = ForwardRef(line, trade_route_ref) + trade_routes.append(trade_route_forward_ref) + + line.add_raw_api_object(trade_route_raw_api_object) + # ===================================================================================== + ability_raw_api_object.add_raw_member("trade_routes", + trade_routes, + "engine.ability.type.TradePost") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/transfer_storage.py b/openage/convert/processor/conversion/aoc/ability/transfer_storage.py new file mode 100644 index 0000000000..2aefa14bc4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/transfer_storage.py @@ -0,0 +1,94 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the TransferStorage ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def transfer_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the TransferStorage 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, None + """ + 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}.TransferStorage" + ability_raw_api_object = RawAPIObject(ability_ref, + "TransferStorage", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.TransferStorage") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # storage element + storage_entity = None + garrisoned_forward_ref = None + for garrisoned in line.garrison_entities: + creatable_type = garrisoned.get_head_unit()["creatable_type"].value + + if creatable_type == 4: + storage_name = name_lookup_dict[garrisoned.get_id()][0] + storage_entity = garrisoned + garrisoned_forward_ref = ForwardRef(storage_entity, storage_name) + + break + + else: + garrisoned = line.garrison_entities[0] + storage_name = name_lookup_dict[garrisoned.get_id()][0] + storage_entity = garrisoned + garrisoned_forward_ref = ForwardRef(storage_entity, storage_name) + + ability_raw_api_object.add_raw_member("storage_element", + garrisoned_forward_ref, + "engine.ability.type.TransferStorage") + + # Source container + source_ref = f"{game_entity_name}.Storage.{game_entity_name}Container" + source_forward_ref = ForwardRef(line, source_ref) + ability_raw_api_object.add_raw_member("source_container", + source_forward_ref, + "engine.ability.type.TransferStorage") + + # Target container + target = None + unit_commands = line.get_switch_unit()["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + # Deposit + if type_id == 136: + target_id = command["unit_id"].value + target = dataset.building_lines[target_id] + + target_name = name_lookup_dict[target.get_id()][0] + target_ref = f"{target_name}.Storage.{target_name}Container" + target_forward_ref = ForwardRef(target, target_ref) + ability_raw_api_object.add_raw_member("target_container", + target_forward_ref, + "engine.ability.type.TransferStorage") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/turn.py b/openage/convert/processor/conversion/aoc/ability/turn.py new file mode 100644 index 0000000000..f80b615e58 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/turn.py @@ -0,0 +1,94 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Turn ability. +""" +from __future__ import annotations +import typing + +from math import degrees + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + +FLOAT32_MAX = 3.4028234663852886e+38 + + +def turn_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Turn 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 = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + 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}.Turn" + ability_raw_api_object = RawAPIObject(ability_ref, + "Turn", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Turn") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Speed + turn_speed_unmodified = current_unit["turn_speed"].value + + # Default case: Instant turning + turn_speed = MemberSpecialValue.NYAN_INF + + # Ships/Trebuchets turn slower + if turn_speed_unmodified > 0: + turn_yaw = current_unit["max_yaw_per_sec_moving"].value + + if not turn_yaw == FLOAT32_MAX: + turn_speed = degrees(turn_yaw) + + ability_raw_api_object.add_raw_member("turn_speed", + turn_speed, + "engine.ability.type.Turn") + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + + # Ability properties + properties = { + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + } + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/use_contingent.py b/openage/convert/processor/conversion/aoc/ability/use_contingent.py new file mode 100644 index 0000000000..4a5716b3e2 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/use_contingent.py @@ -0,0 +1,91 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the UseContingent ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def use_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the UseContingent 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 = line.get_head_unit() + 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}.UseContingent" + + # Check if contingents are stored in the unit before creating the ability + + # Stores the pop space + resource_storage = current_unit["resource_storage"].value + contingents = [] + for storage in resource_storage: + type_id = storage["type"].value + + if type_id == 11: + resource = dataset.pregen_nyan_objects["util.resource.types.PopulationSpace"].get_nyan_object( + ) + resource_name = "PopSpace" + + else: + continue + + amount = storage["amount"].value + + contingent_amount_name = f"{game_entity_name}.UseContingent.{resource_name}" + contingent_amount = RawAPIObject(contingent_amount_name, resource_name, + dataset.nyan_api_objects) + contingent_amount.add_raw_parent("engine.util.resource.ResourceAmount") + ability_forward_ref = ForwardRef(line, ability_ref) + contingent_amount.set_location(ability_forward_ref) + + contingent_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + contingent_amount.add_raw_member("amount", + amount, + "engine.util.resource.ResourceAmount") + + line.add_raw_api_object(contingent_amount) + contingent_amount_forward_ref = ForwardRef(line, + contingent_amount_name) + contingents.append(contingent_amount_forward_ref) + + if not contingents: + # Break out of function if no contingents were found + return None + + ability_raw_api_object = RawAPIObject(ability_ref, + "UseContingent", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.UseContingent") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + ability_raw_api_object.add_raw_member("amount", + contingents, + "engine.ability.type.UseContingent") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability/util.py b/openage/convert/processor/conversion/aoc/ability/util.py new file mode 100644 index 0000000000..75c8c515b9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/util.py @@ -0,0 +1,302 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Helper functions for AoC ability conversion. +""" +from __future__ import annotations +import typing + + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.combined_sprite import CombinedSprite +from .....entity_object.conversion.combined_sound import CombinedSound +from .....entity_object.conversion.converter_object import RawAPIObject, RawMemberPush +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def create_animation( + line: GenieGameEntityGroup, + animation_id: int, + location_ref: str, + obj_name_prefix: str, + filename_prefix: str +) -> ForwardRef: + """ + Generates an animation for an ability. + + :param line: ConverterObjectGroup that the animation object is added to. + :type line: ConverterObjectGroup + :param animation_id: ID of the animation in the dataset. + :type animation_id: int + :param ability_ref: Reference of the object the animation is nested in. + :type ability_ref: str + :param obj_name_prefix: Name prefix for the animation object. + :type obj_name_prefix: str + :param filename_prefix: Prefix for the animation PNG and sprite files. + :type filename_prefix: str + """ + dataset = line.data + head_unit_id = line.get_head_unit_id() + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + animation_ref = f"{location_ref}.{obj_name_prefix}Animation" + animation_obj_name = f"{obj_name_prefix}Animation" + animation_raw_api_object = RawAPIObject(animation_ref, animation_obj_name, + dataset.nyan_api_objects) + animation_raw_api_object.add_raw_parent("engine.util.graphics.Animation") + animation_location = ForwardRef(line, location_ref) + animation_raw_api_object.set_location(animation_location) + + if animation_id in dataset.combined_sprites.keys(): + ability_sprite = dataset.combined_sprites[animation_id] + + else: + ability_sprite = CombinedSprite(animation_id, + (f"{filename_prefix}" + f"{name_lookup_dict[head_unit_id][1]}"), + dataset) + dataset.combined_sprites.update({ability_sprite.get_id(): ability_sprite}) + + ability_sprite.add_reference(animation_raw_api_object) + + animation_raw_api_object.add_raw_member("sprite", ability_sprite, + "engine.util.graphics.Animation") + + line.add_raw_api_object(animation_raw_api_object) + + animation_forward_ref = ForwardRef(line, animation_ref) + + return animation_forward_ref + + +def create_civ_animation( + line: GenieGameEntityGroup, + civ_group: GenieCivilizationGroup, + animation_id: int, + location_ref: str, + obj_name_prefix: str, + filename_prefix: str, + exists: bool = False +) -> None: + """ + Generates an animation as a patch for a civ. + + :param line: ConverterObjectGroup that the animation object is added to. + :type line: ConverterObjectGroup + :param civ_group: ConverterObjectGroup that patches the animation object into the ability. + :type civ_group: ConverterObjectGroup + :param animation_id: ID of the animation in the dataset. + :type animation_id: int + :param location_ref: Reference of the object the resulting object is nested in. + :type location_ref: str + :param obj_name_prefix: Name prefix for the object. + :type obj_name_prefix: str + :param filename_prefix: Prefix for the animation PNG and sprite files. + :type filename_prefix: str + :param exists: Tells the method if the animation object has already been created. + :type exists: bool + """ + dataset = civ_group.data + head_unit_id = line.get_head_unit_id() + civ_id = civ_group.get_id() + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + civ_name = civ_lookup_dict[civ_id][0] + + patch_target_ref = f"{location_ref}" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"{game_entity_name}{obj_name_prefix}AnimationWrapper" + wrapper_ref = f"{civ_name}.{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") + wrapper_raw_api_object.set_location(ForwardRef(civ_group, civ_name)) + + # Nyan patch + nyan_patch_name = f"{game_entity_name}{obj_name_prefix}Animation" + nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(civ_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 animation_id > -1: + # If the animation object already exists, we do not need to create it again + if exists: + # Point to a previously created animation object + animation_ref = f"{location_ref}.{obj_name_prefix}Animation" + animation_forward_ref = ForwardRef(line, animation_ref) + + else: + # Create the animation object + animation_forward_ref = create_animation(line, + animation_id, + location_ref, + obj_name_prefix, + filename_prefix) + + # Patch animation into ability + nyan_patch_raw_api_object.add_raw_patch_member( + "animations", + [animation_forward_ref], + "engine.ability.property.type.Animated", + MemberOperator.ASSIGN + ) + + else: + # No animation -> empty the set + nyan_patch_raw_api_object.add_raw_patch_member( + "animations", + [], + "engine.ability.property.type.Animated", + MemberOperator.ASSIGN + ) + + patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + civ_group.add_raw_api_object(wrapper_raw_api_object) + civ_group.add_raw_api_object(nyan_patch_raw_api_object) + + # Add patch to game_setup + civ_forward_ref = ForwardRef(civ_group, civ_name) + wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) + push_object = RawMemberPush(civ_forward_ref, + "game_setup", + "engine.util.setup.PlayerSetup", + [wrapper_forward_ref]) + civ_group.add_raw_member_push(push_object) + + +def create_sound( + line: GenieGameEntityGroup, + sound_id: int, + location_ref: str, + obj_name_prefix: str, + filename_prefix: str +) -> ForwardRef: + """ + Generates a sound for an ability. + + :param line: ConverterObjectGroup that the animation object is added to. + :type line: ConverterObjectGroup + :param sound_id: ID of the sound in the dataset. + :type sound_id: int + :param location_ref: Reference of the object the sound is nested in. + :type location_ref: str + :param obj_name_prefix: Name prefix for the sound object. + :type obj_name_prefix: str + :param filename_prefix: Prefix for the animation PNG and sprite files. + :type filename_prefix: str + """ + dataset = line.data + + sound_ref = f"{location_ref}.{obj_name_prefix}Sound" + sound_obj_name = f"{obj_name_prefix}Sound" + sound_raw_api_object = RawAPIObject(sound_ref, sound_obj_name, + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(line, location_ref) + sound_raw_api_object.set_location(sound_location) + + # Search for the sound if it exists + sounds_set = [] + + genie_sound = dataset.genie_sounds[sound_id] + file_ids = genie_sound.get_sounds(civ_id=-1) + + for file_id in file_ids: + if file_id in dataset.combined_sounds: + sound = dataset.combined_sounds[file_id] + + else: + sound = CombinedSound(sound_id, + file_id, + f"{filename_prefix}sound_{str(file_id)}", + dataset) + dataset.combined_sounds.update({file_id: sound}) + + sound.add_reference(sound_raw_api_object) + sounds_set.append(sound) + + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + sounds_set, + "engine.util.sound.Sound") + + line.add_raw_api_object(sound_raw_api_object) + + sound_forward_ref = ForwardRef(line, sound_ref) + + return sound_forward_ref + + +def create_language_strings( + line: GenieGameEntityGroup, + string_id: int, + location_ref: str, + obj_name_prefix: str +) -> list[ForwardRef]: + """ + Generates a language string for an ability. + + :param line: ConverterObjectGroup that the animation object is added to. + :type line: ConverterObjectGroup + :param string_id: ID of the string in the dataset. + :type string_id: int + :param location_ref: Reference of the object the string is nested in. + :type location_ref: str + :param obj_name_prefix: Name prefix for the string object. + :type obj_name_prefix: str + """ + dataset = line.data + string_resources = dataset.strings.get_tables() + + string_objs = [] + for language, strings in string_resources.items(): + if string_id in strings.keys(): + string_name = f"{obj_name_prefix}String" + string_ref = f"{location_ref}.{string_name}" + string_raw_api_object = RawAPIObject(string_ref, string_name, + dataset.nyan_api_objects) + string_raw_api_object.add_raw_parent("engine.util.language.LanguageTextPair") + string_location = ForwardRef(line, location_ref) + string_raw_api_object.set_location(string_location) + + # Language identifier + lang_ref = f"util.language.{language}" + lang_forward_ref = dataset.pregen_nyan_objects[lang_ref].get_nyan_object() + string_raw_api_object.add_raw_member("language", + lang_forward_ref, + "engine.util.language.LanguageTextPair") + + # String + string_raw_api_object.add_raw_member("string", + strings[string_id], + "engine.util.language.LanguageTextPair") + + line.add_raw_api_object(string_raw_api_object) + string_forward_ref = ForwardRef(line, string_ref) + string_objs.append(string_forward_ref) + + return string_objs diff --git a/openage/convert/processor/conversion/aoc/ability/visibility.py b/openage/convert/processor/conversion/aoc/ability/visibility.py new file mode 100644 index 0000000000..c13a62134f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/visibility.py @@ -0,0 +1,135 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Visibility ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, GenieAmbientGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def visibility_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Visibility 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 + api_objects = dataset.nyan_api_objects + + 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}.Visibility" + ability_raw_api_object = RawAPIObject(ability_ref, "Visibility", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Visibility") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Units are not visible in fog... + visible = False + + # ...Buidings and scenery is though + if isinstance(line, (GenieBuildingLineGroup, GenieAmbientGroup)): + visible = True + + ability_raw_api_object.add_raw_member("visible_in_fog", visible, + "engine.ability.type.Visibility") + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [ + dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"], + dataset.pregen_nyan_objects["util.diplomatic_stance.types.Friendly"].get_nyan_object(), + dataset.pregen_nyan_objects["util.diplomatic_stance.types.Neutral"].get_nyan_object(), + dataset.pregen_nyan_objects["util.diplomatic_stance.types.Enemy"].get_nyan_object(), + dataset.pregen_nyan_objects["util.diplomatic_stance.types.Gaia"].get_nyan_object() + ] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + + # Ability properties + properties = { + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + } + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Add another Visibility ability for buildings with construction progress = 0.0 + # It is not returned by this method, but referenced by the Constructable ability + if isinstance(line, GenieBuildingLineGroup): + ability_ref = f"{game_entity_name}.VisibilityConstruct0" + ability_raw_api_object = RawAPIObject(ability_ref, + "VisibilityConstruct0", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Visibility") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # The construction site is not visible in fog + visible = False + ability_raw_api_object.add_raw_member("visible_in_fog", visible, + "engine.ability.type.Visibility") + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Only the player and friendly players can see the construction site + diplomatic_stances = [ + dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"], + dataset.pregen_nyan_objects["util.diplomatic_stance.types.Friendly"].get_nyan_object( + ) + ] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + + # Ability properties + properties = { + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + } + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/aoc/ability_subprocessor.py b/openage/convert/processor/conversion/aoc/ability_subprocessor.py index 19e0a6692e..7f04cb8049 100644 --- a/openage/convert/processor/conversion/aoc/ability_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/ability_subprocessor.py @@ -1,41 +1,69 @@ # 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 -# pylint: disable=invalid-name -# -# TODO: -# pylint: disable=unused-argument,line-too-long,too-many-positional-arguments +# pylint: disable=too-few-public-methods, invalid-name """ Derives and adds abilities to lines. Subroutine of the nyan subprocessor. """ -from __future__ import annotations -import typing - -from math import degrees - - -from .....nyan.nyan_structs import MemberSpecialValue, MemberOperator -from .....util.ordered_set import OrderedSet -from ....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, \ - GenieAmbientGroup, GenieGarrisonMode, GenieStackBuildingGroup, \ - GenieUnitLineGroup, GenieMonkGroup, GenieVillagerGroup -from ....entity_object.conversion.combined_sound import CombinedSound -from ....entity_object.conversion.combined_sprite import CombinedSprite -from ....entity_object.conversion.converter_object import RawAPIObject -from ....entity_object.conversion.converter_object import RawMemberPush -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from .effect_subprocessor import AoCEffectSubprocessor -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup - from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup - - -FLOAT32_MAX = 3.4028234663852886e+38 +from .ability.active_transform_to import active_transform_to_ability +from .ability.activity import activity_ability +from .ability.apply_continuous_effect import apply_continuous_effect_ability +from .ability.apply_discrete_effect import apply_discrete_effect_ability +from .ability.attribute_change_tracker import attribute_change_tracker_ability +from .ability.collect_storage import collect_storage_ability +from .ability.collision import collision_ability +from .ability.constructable import constructable_ability +from .ability.create import create_ability +from .ability.death import death_ability +from .ability.delete import delete_ability +from .ability.despawn import despawn_ability +from .ability.drop_resources import drop_resources_ability +from .ability.drop_site import drop_site_ability +from .ability.enter_container import enter_container_ability +from .ability.exchange_resources import exchange_resources_ability +from .ability.exit_container import exit_container_ability +from .ability.game_entity_stance import game_entity_stance_ability +from .ability.formation import formation_ability +from .ability.foundation import foundation_ability +from .ability.gather import gather_ability +from .ability.harvestable import harvestable_ability +from .ability.herd import herd_ability +from .ability.herdable import herdable_ability +from .ability.idle import idle_ability +from .ability.line_of_sight import line_of_sight_ability +from .ability.live import live_ability +from .ability.move import move_ability, move_projectile_ability +from .ability.named import named_ability +from .ability.overlay_terrain import overlay_terrain_ability +from .ability.pathable import pathable_ability +from .ability.production_queue import production_queue_ability +from .ability.projectile import projectile_ability +from .ability.provide_contingent import provide_contingent_ability +from .ability.rally_point import rally_point_ability +from .ability.regenerate_attribute import regenerate_attribute_ability +from .ability.regenerate_resource_spot import regenerate_resource_spot_ability +from .ability.remove_storage import remove_storage_ability +from .ability.research import research_ability +from .ability.resistance import resistance_ability +from .ability.resource_storage import resource_storage_ability +from .ability.restock import restock_ability +from .ability.selectable import selectable_ability +from .ability.send_back_to_task import send_back_to_task_ability +from .ability.shoot_projectile import shoot_projectile_ability +from .ability.stop import stop_ability +from .ability.storage import storage_ability +from .ability.terrain_requirement import terrain_requirement_ability +from .ability.trade import trade_ability +from .ability.trade_post import trade_post_ability +from .ability.transfer_storage import transfer_storage_ability +from .ability.turn import turn_ability +from .ability.use_contingent import use_contingent_ability +from .ability.visibility import visibility_ability + +from .ability.util import create_animation, create_civ_animation, create_language_strings, \ + create_sound class AoCAbilitySubprocessor: @@ -43,7608 +71,63 @@ class AoCAbilitySubprocessor: Creates raw API objects for abilities in AoC. """ - @staticmethod - def active_transform_to_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ActiveTransformTo 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 - """ - # 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, - command_id: int, - ranged: bool = False - ) -> ForwardRef: - """ - Adds the ApplyContinuousEffect 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 - """ - if isinstance(line, GenieVillagerGroup): - current_unit = line.get_units_with_command(command_id)[0] - - else: - current_unit = line.get_head_unit() - - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_name = command_lookup_dict[command_id][0] - - 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("engine.ability.type.ApplyContinuousEffect") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Ability properties - properties = {} - - # Get animation from commands proceed sprite - unit_commands = current_unit["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - if type_id != command_id: - continue - - ability_animation_id = command["proceed_sprite_id"].value - break - - else: - ability_animation_id = -1 - - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - ability_animation_id, - property_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Create custom civ graphics - handled_graphics_set_ids = set() - for civ_group in dataset.civ_groups.values(): - civ = civ_group.civ - civ_id = civ_group.get_id() - - # Only proceed if the civ stores the unit in the line - if current_unit_id not in civ["units"].value.keys(): - continue - - civ_animation_id = civ["units"][current_unit_id]["attack_sprite_id"].value - - if civ_animation_id != ability_animation_id: - # Find the corresponding graphics set - graphics_set_id = -1 - for set_id, items in gset_lookup_dict.items(): - if civ_id in items[0]: - graphics_set_id = set_id - break - - # Check if the object for the animation has been created before - obj_exists = graphics_set_id in handled_graphics_set_ids - if not obj_exists: - handled_graphics_set_ids.add(graphics_set_id) - - obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}{ability_name}" - filename_prefix = (f"{command_lookup_dict[command_id][1]}_" - f"{gset_lookup_dict[graphics_set_id][2]}_") - AoCAbilitySubprocessor.create_civ_animation(line, - civ_group, - civ_animation_id, - property_ref, - obj_prefix, - filename_prefix, - obj_exists) - - # Command Sound - ability_comm_sound_id = current_unit["command_sound_id"].value - if ability_comm_sound_id > -1: - property_ref = f"{ability_ref}.CommandSound" - property_raw_api_object = RawAPIObject(property_ref, - "CommandSound", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - sounds_set = [] - sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, - ability_comm_sound_id, - property_ref, - ability_name, - "command_") - sounds_set.append(sound_forward_ref) - property_raw_api_object.add_raw_member("sounds", sounds_set, - "engine.ability.property.type.CommandSound") - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - 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 - property_raw_api_object.add_raw_member("min_range", - min_range, - "engine.ability.property.type.Ranged") - - # Max range - if command_id == 105: - # Heal - max_range = 4 - - else: - 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 - }) - - # Effects - effects = None - allowed_types = None - if command_id == 101: - # Construct - effects = AoCEffectSubprocessor.get_construct_effects(line, ability_ref) - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( - ) - ] - - elif command_id == 105: - # Heal - effects = AoCEffectSubprocessor.get_heal_effects(line, ability_ref) - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() - ] - - elif command_id == 106: - # Repair - effects = AoCEffectSubprocessor.get_repair_effects(line, ability_ref) - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( - ) - ] - - ability_raw_api_object.add_raw_member("effects", - effects, - "engine.ability.type.ApplyContinuousEffect") - - # Application delay - apply_graphic = dataset.genie_graphics[ability_animation_id] - frame_rate = apply_graphic.get_frame_rate() - frame_delay = current_unit["frame_delay"].value - application_delay = frame_rate * frame_delay - ability_raw_api_object.add_raw_member("application_delay", - application_delay, - "engine.ability.type.ApplyContinuousEffect") - - # Allowed types - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.ApplyContinuousEffect") - ability_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.ability.type.ApplyContinuousEffect") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def apply_discrete_effect_ability( - line: GenieGameEntityGroup, - command_id: int, - ranged: bool = False, - projectile: int = -1 - ) -> ForwardRef: - """ - Adds the ApplyDiscreteEffect 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 - """ - if isinstance(line, GenieVillagerGroup): - current_unit = line.get_units_with_command(command_id)[0] - current_unit_id = current_unit["id0"].value - - else: - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - - head_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - ability_name = command_lookup_dict[command_id][0] - - 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("engine.ability.type.ApplyDiscreteEffect") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - ability_animation_id = current_unit["attack_sprite_id"].value - - else: - ability_ref = (f"{game_entity_name}.ShootProjectile.Projectile{projectile}." - f"{ability_name}") - ability_raw_api_object = RawAPIObject( - ability_ref, ability_name, dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.ApplyDiscreteEffect") - ability_location = ForwardRef( - line, - f"{game_entity_name}.ShootProjectile.Projectile{projectile}" - ) - ability_raw_api_object.set_location(ability_location) - - ability_animation_id = -1 - - line.add_raw_api_object(ability_raw_api_object) - - # Ability properties - properties = {} - - # Animated - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - ability_animation_id, - property_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Create custom civ graphics - handled_graphics_set_ids = set() - for civ_group in dataset.civ_groups.values(): - civ = civ_group.civ - civ_id = civ_group.get_id() - - # Only proceed if the civ stores the unit in the line - if current_unit_id not in civ["units"].value.keys(): - continue - - civ_animation_id = civ["units"][current_unit_id]["attack_sprite_id"].value - - if civ_animation_id != ability_animation_id: - # Find the corresponding graphics set - graphics_set_id = -1 - for set_id, items in gset_lookup_dict.items(): - if civ_id in items[0]: - graphics_set_id = set_id - break - - # Check if the object for the animation has been created before - obj_exists = graphics_set_id in handled_graphics_set_ids - if not obj_exists: - handled_graphics_set_ids.add(graphics_set_id) - - obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}{ability_name}" - filename_prefix = (f"{command_lookup_dict[command_id][1]}_" - f"{gset_lookup_dict[graphics_set_id][2]}_") - AoCAbilitySubprocessor.create_civ_animation(line, - civ_group, - civ_animation_id, - property_ref, - obj_prefix, - filename_prefix, - obj_exists) - - # Command Sound - if projectile == -1: - ability_comm_sound_id = current_unit["command_sound_id"].value - - else: - ability_comm_sound_id = -1 - - if ability_comm_sound_id > -1: - property_ref = f"{ability_ref}.CommandSound" - property_raw_api_object = RawAPIObject(property_ref, - "CommandSound", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - sounds_set = [] - - if projectile == -1: - sound_obj_prefix = ability_name - - else: - sound_obj_prefix = "ProjectileAttack" - - sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, - ability_comm_sound_id, - property_ref, - sound_obj_prefix, - "command_") - sounds_set.append(sound_forward_ref) - property_raw_api_object.add_raw_member("sounds", sounds_set, - "engine.ability.property.type.CommandSound") - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - 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 - 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 - 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" - batch_raw_api_object = RawAPIObject(batch_ref, "Batch", dataset.nyan_api_objects) - batch_raw_api_object.add_raw_parent("engine.util.effect_batch.type.UnorderedBatch") - batch_location = ForwardRef(line, ability_ref) - batch_raw_api_object.set_location(batch_location) - - line.add_raw_api_object(batch_raw_api_object) - - effects = None - if command_id == 7: - # Attack - if projectile != 1: - effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref) - - else: - effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref, projectile=1) - - elif command_id == 104: - # Convert - effects = AoCEffectSubprocessor.get_convert_effects(line, batch_ref) - - batch_raw_api_object.add_raw_member("effects", - effects, - "engine.util.effect_batch.EffectBatch") - - batch_forward_ref = ForwardRef(line, batch_ref) - ability_raw_api_object.add_raw_member("batches", - [batch_forward_ref], - "engine.ability.type.ApplyDiscreteEffect") - - # Reload time - if projectile == -1: - reload_time = current_unit["attack_speed"].value - - else: - reload_time = 0 - - ability_raw_api_object.add_raw_member("reload_time", - reload_time, - "engine.ability.type.ApplyDiscreteEffect") - - # Application delay - if projectile == -1: - attack_graphic_id = current_unit["attack_sprite_id"].value - attack_graphic = dataset.genie_graphics[attack_graphic_id] - frame_rate = attack_graphic.get_frame_rate() - frame_delay = current_unit["frame_delay"].value - application_delay = frame_rate * frame_delay - - else: - application_delay = 0 - - ability_raw_api_object.add_raw_member("application_delay", - application_delay, - "engine.ability.type.ApplyDiscreteEffect") - - # Allowed types (all buildings/units) - if command_id == 104: - # Convert - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() - ] - - else: - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( - ) - ] - - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.ApplyDiscreteEffect") - - if command_id == 104: - # Convert - blacklisted_entities = [] - for unit_line in dataset.unit_lines.values(): - if unit_line.has_command(104): - # Blacklist other monks - blacklisted_name = name_lookup_dict[unit_line.get_head_unit_id()][0] - blacklisted_entities.append(ForwardRef(unit_line, blacklisted_name)) - - elif unit_line.get_class_id() in (13, 55): - # Blacklist siege - blacklisted_name = name_lookup_dict[unit_line.get_head_unit_id()][0] - blacklisted_entities.append(ForwardRef(unit_line, blacklisted_name)) - - else: - blacklisted_entities = [] - - ability_raw_api_object.add_raw_member("blacklisted_entities", - blacklisted_entities, - "engine.ability.type.ApplyDiscreteEffect") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def attribute_change_tracker_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the AttributeChangeTracker 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - 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}.AttributeChangeTracker" - ability_raw_api_object = RawAPIObject(ability_ref, - "AttributeChangeTracker", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.AttributeChangeTracker") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Attribute - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - ability_raw_api_object.add_raw_member("attribute", - attribute, - "engine.ability.type.AttributeChangeTracker") - - # Change progress - damage_graphics = current_unit["damage_graphics"].value - progress_forward_refs = [] - - # Damage graphics are ordered ascending, so we start from 0 - interval_left_bound = 0 - for damage_graphic_member in damage_graphics: - interval_right_bound = damage_graphic_member["damage_percent"].value - progress_ref = f"{ability_ref}.ChangeProgress{interval_right_bound}" - progress_raw_api_object = RawAPIObject(progress_ref, - f"ChangeProgress{interval_right_bound}", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.AttributeChange"], - "engine.util.progress.Progress") - - # Interval - progress_raw_api_object.add_raw_member("left_boundary", - interval_left_bound, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - interval_right_bound, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # AnimationOverlay property - # ===================================================================================== - progress_animation_id = damage_graphic_member["graphic_id"].value - if progress_animation_id > -1: - property_ref = f"{progress_ref}.AnimationOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "AnimationOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.AnimationOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - progress_animation_id, - property_ref, - "Idle", - f"idle_damage_override_{interval_right_bound}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("overlays", - animations_set, - "engine.util.progress.property.type.AnimationOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.AnimationOverlay"]: property_forward_ref - }) - - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - interval_left_bound = interval_right_bound - - ability_raw_api_object.add_raw_member("change_progress", - progress_forward_refs, - "engine.ability.type.AttributeChangeTracker") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def collect_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the CollectStorage 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}.CollectStorage" - ability_raw_api_object = RawAPIObject(ability_ref, - "CollectStorage", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.CollectStorage") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Container - container_ref = f"{game_entity_name}.Storage.{game_entity_name}Container" - container_forward_ref = ForwardRef(line, container_ref) - ability_raw_api_object.add_raw_member("container", - container_forward_ref, - "engine.ability.type.CollectStorage") - - # Storage elements - elements = [] - entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) - for entity in line.garrison_entities: - entity_ref = entity_lookups[entity.get_head_unit_id()][0] - entity_forward_ref = ForwardRef(entity, entity_ref) - elements.append(entity_forward_ref) - - ability_raw_api_object.add_raw_member("storage_elements", - elements, - "engine.ability.type.CollectStorage") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def collision_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Collision 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 = line.get_head_unit() - 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}.Collision" - ability_raw_api_object = RawAPIObject(ability_ref, "Collision", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Collision") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Hitbox object - hitbox_name = f"{game_entity_name}.Collision.{game_entity_name}Hitbox" - hitbox_raw_api_object = RawAPIObject(hitbox_name, - f"{game_entity_name}Hitbox", - dataset.nyan_api_objects) - hitbox_raw_api_object.add_raw_parent("engine.util.hitbox.Hitbox") - hitbox_location = ForwardRef(line, ability_ref) - hitbox_raw_api_object.set_location(hitbox_location) - - radius_x = current_unit["radius_x"].value - radius_y = current_unit["radius_y"].value - radius_z = current_unit["radius_z"].value - - hitbox_raw_api_object.add_raw_member("radius_x", - radius_x, - "engine.util.hitbox.Hitbox") - hitbox_raw_api_object.add_raw_member("radius_y", - radius_y, - "engine.util.hitbox.Hitbox") - hitbox_raw_api_object.add_raw_member("radius_z", - radius_z, - "engine.util.hitbox.Hitbox") - - hitbox_forward_ref = ForwardRef(line, hitbox_name) - ability_raw_api_object.add_raw_member("hitbox", - hitbox_forward_ref, - "engine.ability.type.Collision") - - line.add_raw_api_object(hitbox_raw_api_object) - 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 constructable_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Constructable 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - 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}.Constructable" - ability_raw_api_object = RawAPIObject( - ability_ref, "Constructable", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Constructable") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Starting progress (always 0) - ability_raw_api_object.add_raw_member("starting_progress", - 0, - "engine.ability.type.Constructable") - - construction_animation_id = current_unit["construction_graphic_id"].value - - # Construction progress - progress_forward_refs = [] - if line.get_class_id() == 49: - # Farms - # ===================================================================================== - progress_ref = f"{ability_ref}.ConstructionProgress0" - progress_raw_api_object = RawAPIObject(progress_ref, - "ConstructionProgress0", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Construct"], - "engine.util.progress.Progress") - - # Interval = (0.0, 0.0) - progress_raw_api_object.add_raw_member("left_boundary", - 0.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 0.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction1" - terrain_group = dataset.terrain_groups[29] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - # ===================================================================================== - init_state_name = f"{ability_ref}.InitState" - init_state_raw_api_object = RawAPIObject(init_state_name, - "InitState", - dataset.nyan_api_objects) - init_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") - init_state_location = ForwardRef(line, property_ref) - init_state_raw_api_object.set_location(init_state_location) - - line.add_raw_api_object(init_state_raw_api_object) - - # Priority - init_state_raw_api_object.add_raw_member("priority", - 1, - "engine.util.state_machine.StateChanger") - - # Enabled abilities - enabled_forward_refs = [ - ForwardRef(line, - f"{game_entity_name}.VisibilityConstruct0") - ] - init_state_raw_api_object.add_raw_member("enable_abilities", - enabled_forward_refs, - "engine.util.state_machine.StateChanger") - - # Disabled abilities - disabled_forward_refs = [ - ForwardRef(line, - f"{game_entity_name}.AttributeChangeTracker"), - ForwardRef(line, - f"{game_entity_name}.LineOfSight"), - ForwardRef(line, - f"{game_entity_name}.Visibility") - ] - if len(line.creates) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Create")) - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.ProductionQueue")) - - if len(line.researches) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Research")) - - if line.is_projectile_shooter(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Attack")) - - if line.is_garrison(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Storage")) - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RemoveStorage")) - - garrison_mode = line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.NATURAL: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.SendBackToTask")) - - if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RallyPoint")) - - if line.is_harvestable(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Harvestable")) - - if line.is_dropsite(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.DropSite")) - - if line.is_trade_post(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.TradePost")) - - init_state_raw_api_object.add_raw_member("disable_abilities", - disabled_forward_refs, - "engine.util.state_machine.StateChanger") - - # Enabled modifiers - init_state_raw_api_object.add_raw_member("enable_modifiers", - [], - "engine.util.state_machine.StateChanger") - - # Disabled modifiers - init_state_raw_api_object.add_raw_member("disable_modifiers", - [], - "engine.util.state_machine.StateChanger") - # ===================================================================================== - init_state_forward_ref = ForwardRef(line, init_state_name) - property_raw_api_object.add_raw_member("state_change", - init_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.ConstructionProgress33" - progress_raw_api_object = RawAPIObject(progress_ref, - "ConstructionProgress33", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Construct"], - "engine.util.progress.Progress") - - # Interval = (0.0, 33.0) - progress_raw_api_object.add_raw_member("left_boundary", - 0.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 33.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction1" - terrain_group = dataset.terrain_groups[29] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - # ===================================================================================== - construct_state_name = f"{ability_ref}.ConstructState" - construct_state_raw_api_object = RawAPIObject(construct_state_name, - "ConstructState", - dataset.nyan_api_objects) - construct_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") - construct_state_location = ForwardRef(line, ability_ref) - construct_state_raw_api_object.set_location(construct_state_location) - - line.add_raw_api_object(construct_state_raw_api_object) - - # Priority - construct_state_raw_api_object.add_raw_member("priority", - 1, - "engine.util.state_machine.StateChanger") - - # Enabled abilities - construct_state_raw_api_object.add_raw_member("enable_abilities", - [], - "engine.util.state_machine.StateChanger") - - # Disabled abilities - disabled_forward_refs = [ForwardRef(line, - f"{game_entity_name}.AttributeChangeTracker")] - if len(line.creates) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Create")) - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.ProductionQueue")) - if len(line.researches) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Research")) - - if line.is_projectile_shooter(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Attack")) - - if line.is_garrison(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Storage")) - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RemoveStorage")) - - garrison_mode = line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.NATURAL: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.SendBackToTask")) - - if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RallyPoint")) - - if line.is_harvestable(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Harvestable")) - - if line.is_dropsite(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.DropSite")) - - if line.is_trade_post(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.TradePost")) - - construct_state_raw_api_object.add_raw_member("disable_abilities", - disabled_forward_refs, - "engine.util.state_machine.StateChanger") - - # Enabled modifiers - construct_state_raw_api_object.add_raw_member("enable_modifiers", - [], - "engine.util.state_machine.StateChanger") - - # Disabled modifiers - construct_state_raw_api_object.add_raw_member("disable_modifiers", - [], - "engine.util.state_machine.StateChanger") - - # ===================================================================================== - construct_state_forward_ref = ForwardRef(line, construct_state_name) - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.ConstructionProgress66" - progress_raw_api_object = RawAPIObject(progress_ref, - "ConstructionProgress66", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Construct"], - "engine.util.progress.Progress") - - # Interval = (33.0, 66.0) - progress_raw_api_object.add_raw_member("left_boundary", - 33.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 66.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction2" - terrain_group = dataset.terrain_groups[30] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.ConstructionProgress100" - progress_raw_api_object = RawAPIObject(progress_ref, - "ConstructionProgress100", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Construct"], - "engine.util.progress.Progress") - - # Interval = (66.0, 100.0) - progress_raw_api_object.add_raw_member("left_boundary", - 66.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 100.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction3" - terrain_group = dataset.terrain_groups[31] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - - else: - progress_ref = f"{ability_ref}.ConstructionProgress0" - progress_raw_api_object = RawAPIObject(progress_ref, - "ConstructionProgress0", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Construct"], - "engine.util.progress.Progress") - - # Interval = (0.0, 0.0) - progress_raw_api_object.add_raw_member("left_boundary", - 0.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 0.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ================================================================================= - # Idle override - # ================================================================================= - if construction_animation_id > -1: - property_ref = f"{progress_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.Animated") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - overrides = [] - override_ref = f"{property_ref}.IdleOverride" - override_raw_api_object = RawAPIObject(override_ref, - "IdleOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, property_ref) - override_raw_api_object.set_location(override_location) - - line.add_raw_api_object(override_raw_api_object) - - idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") - override_raw_api_object.add_raw_member("ability", - idle_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - construction_animation_id, - override_ref, - "Idle", - "idle_construct0_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - override_forward_ref = ForwardRef(line, override_ref) - overrides.append(override_forward_ref) - # ================================================================================= - property_raw_api_object.add_raw_member("overrides", - overrides, - "engine.util.progress.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref - }) - - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - # ===================================================================================== - init_state_name = f"{ability_ref}.InitState" - init_state_raw_api_object = RawAPIObject(init_state_name, - "InitState", - dataset.nyan_api_objects) - init_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") - init_state_location = ForwardRef(line, property_ref) - init_state_raw_api_object.set_location(init_state_location) - - line.add_raw_api_object(init_state_raw_api_object) - - # Priority - init_state_raw_api_object.add_raw_member("priority", - 1, - "engine.util.state_machine.StateChanger") - - # Enabled abilities - enabled_forward_refs = [ - ForwardRef(line, - f"{game_entity_name}.VisibilityConstruct0") - ] - init_state_raw_api_object.add_raw_member("enable_abilities", - enabled_forward_refs, - "engine.util.state_machine.StateChanger") - - # Disabled abilities - disabled_forward_refs = [ - ForwardRef(line, - f"{game_entity_name}.AttributeChangeTracker"), - ForwardRef(line, - f"{game_entity_name}.LineOfSight"), - ForwardRef(line, - f"{game_entity_name}.Visibility") - ] - if len(line.creates) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Create")) - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.ProductionQueue")) - if len(line.researches) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Research")) - - if line.is_projectile_shooter(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Attack")) - - if line.is_garrison(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Storage")) - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RemoveStorage")) - - garrison_mode = line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.NATURAL: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.SendBackToTask")) - - if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RallyPoint")) - - if line.is_harvestable(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Harvestable")) - - if line.is_dropsite(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.DropSite")) - - if line.is_trade_post(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.TradePost")) - - init_state_raw_api_object.add_raw_member("disable_abilities", - disabled_forward_refs, - "engine.util.state_machine.StateChanger") - - # Enabled modifiers - init_state_raw_api_object.add_raw_member("enable_modifiers", - [], - "engine.util.state_machine.StateChanger") - - # Disabled modifiers - init_state_raw_api_object.add_raw_member("disable_modifiers", - [], - "engine.util.state_machine.StateChanger") - # ===================================================================================== - init_state_forward_ref = ForwardRef(line, init_state_name) - property_raw_api_object.add_raw_member("state_change", - init_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.ConstructionProgress25" - progress_raw_api_object = RawAPIObject(progress_ref, - "ConstructionProgress25", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Construct"], - "engine.util.progress.Progress") - - # Interval = (0.0, 25.0) - progress_raw_api_object.add_raw_member("left_boundary", - 0.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 25.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ================================================================================= - # Idle override - # ================================================================================= - if construction_animation_id > -1: - property_ref = f"{progress_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.Animated") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - overrides = [] - override_ref = f"{progress_ref}.IdleOverride" - override_raw_api_object = RawAPIObject(override_ref, - "IdleOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, property_ref) - override_raw_api_object.set_location(override_location) - - line.add_raw_api_object(override_raw_api_object) - - idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") - override_raw_api_object.add_raw_member("ability", - idle_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - construction_animation_id, - override_ref, - "Idle", - "idle_construct25_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - override_forward_ref = ForwardRef(line, override_ref) - overrides.append(override_forward_ref) - # ================================================================================= - property_raw_api_object.add_raw_member("overrides", - overrides, - "engine.util.progress.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref - }) - - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - # ===================================================================================== - construct_state_name = f"{ability_ref}.ConstructState" - construct_state_raw_api_object = RawAPIObject(construct_state_name, - "ConstructState", - dataset.nyan_api_objects) - construct_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") - construct_state_location = ForwardRef(line, property_ref) - construct_state_raw_api_object.set_location(construct_state_location) - - line.add_raw_api_object(construct_state_raw_api_object) - - # Priority - construct_state_raw_api_object.add_raw_member("priority", - 1, - "engine.util.state_machine.StateChanger") - - # Enabled abilities - construct_state_raw_api_object.add_raw_member("enable_abilities", - [], - "engine.util.state_machine.StateChanger") - - # Disabled abilities - disabled_forward_refs = [ForwardRef(line, - f"{game_entity_name}.AttributeChangeTracker")] - if len(line.creates) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Create")) - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.ProductionQueue")) - if len(line.researches) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Research")) - - if line.is_projectile_shooter(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Attack")) - - if line.is_garrison(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Storage")) - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RemoveStorage")) - - garrison_mode = line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.NATURAL: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.SendBackToTask")) - - if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RallyPoint")) - - if line.is_harvestable(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Harvestable")) - - if line.is_dropsite(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.DropSite")) - - if line.is_trade_post(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.TradePost")) - - construct_state_raw_api_object.add_raw_member("disable_abilities", - disabled_forward_refs, - "engine.util.state_machine.StateChanger") - - # Enabled modifiers - construct_state_raw_api_object.add_raw_member("enable_modifiers", - [], - "engine.util.state_machine.StateChanger") - - # Disabled modifiers - construct_state_raw_api_object.add_raw_member("disable_modifiers", - [], - "engine.util.state_machine.StateChanger") - # ===================================================================================== - construct_state_forward_ref = ForwardRef(line, construct_state_name) - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.ConstructionProgress50" - progress_raw_api_object = RawAPIObject(progress_ref, - "ConstructionProgress50", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Construct"], - "engine.util.progress.Progress") - - # Interval = (25.0, 50.0) - progress_raw_api_object.add_raw_member("left_boundary", - 25.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 50.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ================================================================================= - # Idle override - # ================================================================================= - if construction_animation_id > -1: - property_ref = f"{progress_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.Animated") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - overrides = [] - override_ref = f"{progress_ref}.IdleOverride" - override_raw_api_object = RawAPIObject(override_ref, - "IdleOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, property_ref) - override_raw_api_object.set_location(override_location) - - line.add_raw_api_object(override_raw_api_object) - - idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") - override_raw_api_object.add_raw_member("ability", - idle_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - construction_animation_id, - override_ref, - "Idle", - "idle_construct50_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - override_forward_ref = ForwardRef(line, override_ref) - overrides.append(override_forward_ref) - # ================================================================================= - property_raw_api_object.add_raw_member("overrides", - overrides, - "engine.util.progress.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref - }) - - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.ConstructionProgress75" - progress_raw_api_object = RawAPIObject(progress_ref, - "ConstructionProgress75", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Construct"], - "engine.util.progress.Progress") - - # Interval = (50.0, 75.0) - progress_raw_api_object.add_raw_member("left_boundary", - 50.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 75.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ================================================================================= - # Idle override - # ================================================================================= - if construction_animation_id > -1: - property_ref = f"{progress_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.Animated") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - overrides = [] - override_ref = f"{progress_ref}.IdleOverride" - override_raw_api_object = RawAPIObject(override_ref, - "IdleOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, property_ref) - override_raw_api_object.set_location(override_location) - - line.add_raw_api_object(override_raw_api_object) - - idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") - override_raw_api_object.add_raw_member("ability", - idle_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - construction_animation_id, - override_ref, - "Idle", - "idle_construct75_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - override_forward_ref = ForwardRef(line, override_ref) - overrides.append(override_forward_ref) - # ================================================================================= - property_raw_api_object.add_raw_member("overrides", - overrides, - "engine.util.progress.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref - }) - - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.ConstructionProgress100" - progress_raw_api_object = RawAPIObject(progress_ref, - "ConstructionProgress100", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Construct"], - "engine.util.progress.Progress") - - # Interval = (75.0, 100.0) - progress_raw_api_object.add_raw_member("left_boundary", - 75.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 100.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ================================================================================= - # Idle override - # ================================================================================= - if construction_animation_id > -1: - property_ref = f"{progress_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.Animated") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - overrides = [] - override_ref = f"{progress_ref}.IdleOverride" - override_raw_api_object = RawAPIObject(override_ref, - "IdleOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, progress_ref) - override_raw_api_object.set_location(override_location) - - line.add_raw_api_object(override_raw_api_object) - - idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") - override_raw_api_object.add_raw_member("ability", - idle_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - construction_animation_id, - override_ref, - "Idle", - "idle_construct100_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - override_forward_ref = ForwardRef(line, override_ref) - overrides.append(override_forward_ref) - # ================================================================================= - property_raw_api_object.add_raw_member("overrides", - overrides, - "engine.util.progress.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref - }) - - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - ability_raw_api_object.add_raw_member("construction_progress", - progress_forward_refs, - "engine.ability.type.Constructable") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def create_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Create 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 - api_objects = dataset.nyan_api_objects - - 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}.Create" - ability_raw_api_object = RawAPIObject(ability_ref, "Create", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Create") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties = { - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - } - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Creatables - creatables_set = [] - for creatable in line.creates: - if creatable.is_unique(): - # Skip this because unique units are handled by civs - continue - - # CreatableGameEntity objects are created for each unit/building - # line individually to avoid duplicates. We just point to the - # raw API objects here. - creatable_id = creatable.get_head_unit_id() - creatable_name = name_lookup_dict[creatable_id][0] - - raw_api_object_ref = f"{creatable_name}.CreatableGameEntity" - creatable_forward_ref = ForwardRef(creatable, - raw_api_object_ref) - creatables_set.append(creatable_forward_ref) - - ability_raw_api_object.add_raw_member("creatables", creatables_set, - "engine.ability.type.Create") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def death_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds a PassiveTransformTo ability to a line that is used to make entities die. - - :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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.Death" - ability_raw_api_object = RawAPIObject(ability_ref, "Death", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.PassiveTransformTo") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Ability properties - properties = {} - - # Animation - ability_animation_id = current_unit["dying_graphic"].value - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - ability_animation_id, - ability_ref, - "Death", - "death_") - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Create custom civ graphics - handled_graphics_set_ids = set() - for civ_group in dataset.civ_groups.values(): - civ = civ_group.civ - civ_id = civ_group.get_id() - - # Only proceed if the civ stores the unit in the line - if current_unit_id not in civ["units"].value.keys(): - continue - - civ_animation_id = civ["units"][current_unit_id]["dying_graphic"].value - - if civ_animation_id != ability_animation_id: - # Find the corresponding graphics set - graphics_set_id = -1 - for set_id, items in gset_lookup_dict.items(): - if civ_id in items[0]: - graphics_set_id = set_id - break - - # Check if the object for the animation has been created before - obj_exists = graphics_set_id in handled_graphics_set_ids - if not obj_exists: - handled_graphics_set_ids.add(graphics_set_id) - - obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}Death" - filename_prefix = f"death_{gset_lookup_dict[graphics_set_id][2]}_" - AoCAbilitySubprocessor.create_civ_animation(line, - civ_group, - civ_animation_id, - property_ref, - obj_prefix, - filename_prefix, - obj_exists) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Death condition - death_condition = [ - dataset.pregen_nyan_objects["util.logic.literal.death.StandardHealthDeathLiteral"].get_nyan_object( - ) - ] - ability_raw_api_object.add_raw_member("condition", - death_condition, - "engine.ability.type.PassiveTransformTo") - - # Transform time - # Use the time of the dying graphics - if ability_animation_id > -1: - dying_animation = dataset.genie_graphics[ability_animation_id] - death_time = dying_animation.get_animation_length() - - else: - death_time = 0.0 - - ability_raw_api_object.add_raw_member("transform_time", - death_time, - "engine.ability.type.PassiveTransformTo") - - # Target state - # ===================================================================================== - target_state_name = f"{game_entity_name}.Death.DeadState" - target_state_raw_api_object = RawAPIObject(target_state_name, - "DeadState", - dataset.nyan_api_objects) - target_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") - target_state_location = ForwardRef(line, ability_ref) - target_state_raw_api_object.set_location(target_state_location) - - # Priority - target_state_raw_api_object.add_raw_member("priority", - 1000, - "engine.util.state_machine.StateChanger") - - # Enabled abilities - target_state_raw_api_object.add_raw_member("enable_abilities", - [], - "engine.util.state_machine.StateChanger") - - # Disabled abilities - disabled_forward_refs = [] - if isinstance(line, (GenieUnitLineGroup, GenieBuildingLineGroup)): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.LineOfSight")) - - if isinstance(line, GenieBuildingLineGroup): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.AttributeChangeTracker")) - - if len(line.creates) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Create")) - - if isinstance(line, GenieBuildingLineGroup): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.ProductionQueue")) - if len(line.researches) > 0: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Research")) - - if line.is_projectile_shooter(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Attack")) - - if line.is_garrison(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Storage")) - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RemoveStorage")) - - garrison_mode = line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.NATURAL: - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.SendBackToTask")) - - if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.RallyPoint")) - - if line.is_harvestable(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Harvestable")) - - if isinstance(line, GenieBuildingLineGroup) and line.is_dropsite(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.DropSite")) - - if isinstance(line, GenieBuildingLineGroup) and line.is_trade_post(): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.TradePost")) - - target_state_raw_api_object.add_raw_member("disable_abilities", - disabled_forward_refs, - "engine.util.state_machine.StateChanger") - - # Enabled modifiers - target_state_raw_api_object.add_raw_member("enable_modifiers", - [], - "engine.util.state_machine.StateChanger") - - # Disabled modifiers - target_state_raw_api_object.add_raw_member("disable_modifiers", - [], - "engine.util.state_machine.StateChanger") - - line.add_raw_api_object(target_state_raw_api_object) - # ===================================================================================== - target_state_forward_ref = ForwardRef(line, target_state_name) - ability_raw_api_object.add_raw_member("target_state", - target_state_forward_ref, - "engine.ability.type.PassiveTransformTo") - - # Transform progress - # ===================================================================================== - progress_ref = f"{ability_ref}.DeathProgress" - progress_raw_api_object = RawAPIObject(progress_ref, - "DeathProgress", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.AttributeChange"], - "engine.util.progress.Progress") - - # Interval = (0.0, 100.0) - progress_raw_api_object.add_raw_member("left_boundary", - 0.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 100.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change = target state - property_raw_api_object.add_raw_member("state_change", - target_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - # ===================================================================================== - progress_forward_ref = ForwardRef(line, progress_ref) - ability_raw_api_object.add_raw_member("transform_progress", - [progress_forward_ref], - "engine.ability.type.PassiveTransformTo") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def delete_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds a PassiveTransformTo ability to a line that is used to make entities die. - - :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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - 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}.Delete" - ability_raw_api_object = RawAPIObject(ability_ref, "Delete", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.ActiveTransformTo") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Ability properties - properties = {} - - # Animation - ability_animation_id = current_unit["dying_graphic"].value - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Use the animation from Death ability - animations_set = [] - animation_ref = f"{game_entity_name}.Death.DeathAnimation" - animation_forward_ref = ForwardRef(line, animation_ref) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Transform time - # Use the time of the dying graphics - if ability_animation_id > -1: - dying_animation = dataset.genie_graphics[ability_animation_id] - death_time = dying_animation.get_animation_length() - - else: - death_time = 0.0 - - ability_raw_api_object.add_raw_member("transform_time", - death_time, - "engine.ability.type.ActiveTransformTo") - - # Target state (reuse from Death) - target_state_ref = f"{game_entity_name}.Death.DeadState" - target_state_forward_ref = ForwardRef(line, target_state_ref) - ability_raw_api_object.add_raw_member("target_state", - target_state_forward_ref, - "engine.ability.type.ActiveTransformTo") - - # Transform progress (reuse from Death) - progress_ref = f"{game_entity_name}.Death.DeathProgress" - progress_forward_ref = ForwardRef(line, progress_ref) - ability_raw_api_object.add_raw_member("transform_progress", - [progress_forward_ref], - "engine.ability.type.ActiveTransformTo") - - 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 despawn_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Despawn 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - # Animation and time come from dead unit - death_animation_id = current_unit["dying_graphic"].value - dead_unit_id = current_unit["dead_unit_id"].value - dead_unit = None - if dead_unit_id > -1: - dead_unit = dataset.genie_units[dead_unit_id] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.Despawn" - ability_raw_api_object = RawAPIObject(ability_ref, "Despawn", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Despawn") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Ability properties - properties = {} - - # Animation - ability_animation_id = -1 - if dead_unit: - ability_animation_id = dead_unit["idle_graphic0"].value - - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - ability_animation_id, - property_ref, - "Despawn", - "despawn_") - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", - animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Create custom civ graphics - handled_graphics_set_ids = set() - for civ_group in dataset.civ_groups.values(): - civ = civ_group.civ - civ_id = civ_group.get_id() - - # Only proceed if the civ stores the unit in the line - if current_unit_id not in civ["units"].value.keys(): - continue - - civ_unit = civ["units"][current_unit_id] - civ_dead_unit_id = civ_unit["dead_unit_id"].value - civ_dead_unit = None - if civ_dead_unit_id > -1: - civ_dead_unit = dataset.genie_units[civ_dead_unit_id] - - civ_animation_id = civ_dead_unit["idle_graphic0"].value - - if civ_animation_id != ability_animation_id: - # Find the corresponding graphics set - graphics_set_id = -1 - for set_id, items in gset_lookup_dict.items(): - if civ_id in items[0]: - graphics_set_id = set_id - break - - # Check if the object for the animation has been created before - obj_exists = graphics_set_id in handled_graphics_set_ids - if not obj_exists: - handled_graphics_set_ids.add(graphics_set_id) - - obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}Despawn" - filename_prefix = f"despawn_{gset_lookup_dict[graphics_set_id][2]}_" - AoCAbilitySubprocessor.create_civ_animation(line, - civ_group, - civ_animation_id, - property_ref, - obj_prefix, - filename_prefix, - obj_exists) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Activation condition - # Uses the death condition of the units - activation_condition = [ - dataset.pregen_nyan_objects["util.logic.literal.death.StandardHealthDeathLiteral"].get_nyan_object( - ) - ] - ability_raw_api_object.add_raw_member("activation_condition", - activation_condition, - "engine.ability.type.Despawn") - - # Despawn condition - ability_raw_api_object.add_raw_member("despawn_condition", - [], - "engine.ability.type.Despawn") - - # Despawn time = corpse decay time (dead unit) or Death animation time (if no dead unit exist) - despawn_time = 0 - if dead_unit: - resource_storage = dead_unit["resource_storage"].value - for storage in resource_storage: - resource_id = storage["type"].value - - if resource_id == 12: - despawn_time = storage["amount"].value - - elif death_animation_id > -1: - despawn_time = dataset.genie_graphics[death_animation_id].get_animation_length() - - ability_raw_api_object.add_raw_member("despawn_time", - despawn_time, - "engine.ability.type.Despawn") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def drop_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the DropResources 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 - """ - if isinstance(line, GenieVillagerGroup): - gatherers = line.variants[0].line - - else: - gatherers = [line.line[0]] - - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.DropResources" - ability_raw_api_object = RawAPIObject(ability_ref, - "DropResources", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.DropResources") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Resource containers - containers = [] - for gatherer in gatherers: - unit_commands = gatherer["unit_commands"].value - - for command in unit_commands: - # Find a gather ability. It doesn't matter which one because - # they should all produce the same resource for one genie unit. - type_id = command["type"].value - - if type_id in (5, 110): - break - - gatherer_unit_id = gatherer.get_id() - if gatherer_unit_id not in gather_lookup_dict: - # Skips hunting wolves - continue - - container_ref = (f"{game_entity_name}.ResourceStorage." - f"{gather_lookup_dict[gatherer_unit_id][0]}Container") - container_forward_ref = ForwardRef(line, container_ref) - containers.append(container_forward_ref) - - ability_raw_api_object.add_raw_member("containers", - containers, - "engine.ability.type.DropResources") - - # Search range - ability_raw_api_object.add_raw_member("search_range", - MemberSpecialValue.NYAN_INF, - "engine.ability.type.DropResources") - - # Allowed types - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.DropSite"].get_nyan_object() - ] - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.DropResources") - # Blacklisted enties - ability_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.ability.type.DropResources") - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties = { - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - } - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def drop_site_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the DropSite 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 - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.DropSite" - ability_raw_api_object = RawAPIObject(ability_ref, "DropSite", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.DropSite") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Resource containers - gatherer_ids = line.get_gatherer_ids() - - containers = [] - for gatherer_id in gatherer_ids: - if gatherer_id not in gather_lookup_dict: - # Skips hunting wolves - continue - - gatherer_line = dataset.unit_ref[gatherer_id] - gatherer_head_unit_id = gatherer_line.get_head_unit_id() - gatherer_name = name_lookup_dict[gatherer_head_unit_id][0] - - container_ref = (f"{gatherer_name}.ResourceStorage." - f"{gather_lookup_dict[gatherer_id][0]}Container") - container_forward_ref = ForwardRef(gatherer_line, container_ref) - containers.append(container_forward_ref) - - ability_raw_api_object.add_raw_member("accepts_from", - containers, - "engine.ability.type.DropSite") - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties = { - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - } - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def enter_container_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the EnterContainer 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. None if no valid containers were found. - :rtype: ...dataformat.forward_ref.ForwardRef, None - """ - 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}.EnterContainer" - ability_raw_api_object = RawAPIObject(ability_ref, - "EnterContainer", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.EnterContainer") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Containers - containers = [] - entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) - for garrison in line.garrison_locations: - garrison_mode = garrison.get_garrison_mode() - - # Cannot enter production buildings or monk inventories - if garrison_mode in (GenieGarrisonMode.SELF_PRODUCED, GenieGarrisonMode.MONK): - continue - - garrison_name = entity_lookups[garrison.get_head_unit_id()][0] - - container_ref = f"{garrison_name}.Storage.{garrison_name}Container" - container_forward_ref = ForwardRef(garrison, container_ref) - containers.append(container_forward_ref) - - if not containers: - return None - - ability_raw_api_object.add_raw_member("allowed_containers", - containers, - "engine.ability.type.EnterContainer") - - # Allowed types (all buildings/units) - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object() - ] - - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.EnterContainer") - ability_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.ability.type.EnterContainer") - - 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 exchange_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ExchangeResources 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] - - resource_names = ["Food", "Wood", "Stone"] - - abilities = [] - for resource_name in resource_names: - ability_name = f"MarketExchange{resource_name}" - 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("engine.ability.type.ExchangeResources") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Resource that is exchanged (resource A) - resource_a = dataset.pregen_nyan_objects[f"util.resource.types.{resource_name}"].get_nyan_object( - ) - ability_raw_api_object.add_raw_member("resource_a", - resource_a, - "engine.ability.type.ExchangeResources") - - # Resource that is exchanged for (resource B) - resource_b = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() - ability_raw_api_object.add_raw_member("resource_b", - resource_b, - "engine.ability.type.ExchangeResources") - - # Exchange rate - exchange_rate_ref = f"util.resource.market_trading.Market{resource_name}ExchangeRate" - exchange_rate = dataset.pregen_nyan_objects[exchange_rate_ref].get_nyan_object() - ability_raw_api_object.add_raw_member("exchange_rate", - exchange_rate, - "engine.ability.type.ExchangeResources") - - # Exchange modes - buy_exchange_ref = "util.resource.market_trading.MarketBuyExchangeMode" - sell_exchange_ref = "util.resource.market_trading.MarketSellExchangeMode" - exchange_modes = [ - dataset.pregen_nyan_objects[buy_exchange_ref].get_nyan_object(), - dataset.pregen_nyan_objects[sell_exchange_ref].get_nyan_object(), - ] - ability_raw_api_object.add_raw_member("exchange_modes", - exchange_modes, - "engine.ability.type.ExchangeResources") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - abilities.append(ability_forward_ref) - - return abilities - - @staticmethod - def exit_container_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ExitContainer 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. None if no valid containers were found. - :rtype: ...dataformat.forward_ref.ForwardRef, None - """ - 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}.ExitContainer" - ability_raw_api_object = RawAPIObject(ability_ref, - "ExitContainer", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.ExitContainer") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Containers - containers = [] - entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) - for garrison in line.garrison_locations: - garrison_mode = garrison.get_garrison_mode() - - # Cannot enter production buildings or monk inventories - if garrison_mode == GenieGarrisonMode.MONK: - continue - - garrison_name = entity_lookups[garrison.get_head_unit_id()][0] - - container_ref = f"{garrison_name}.Storage.{garrison_name}Container" - container_forward_ref = ForwardRef(garrison, container_ref) - containers.append(container_forward_ref) - - if not containers: - return None - - ability_raw_api_object.add_raw_member("allowed_containers", - containers, - "engine.ability.type.ExitContainer") - - 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 game_entity_stance_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the GameEntityStance 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 = line.get_head_unit() - 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}.GameEntityStance" - ability_raw_api_object = RawAPIObject(ability_ref, - "GameEntityStance", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.GameEntityStance") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Stances - search_range = current_unit["search_radius"].value - stance_names = ["Aggressive", "Defensive", "StandGround", "Passive"] - - # Attacking is prefered - ability_preferences = [] - if line.is_projectile_shooter(): - ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Attack")) - - elif line.is_melee() or line.is_ranged(): - if line.has_command(7): - ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Attack")) - - if line.has_command(105): - ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Heal")) - - # Units are prefered before buildings - type_preferences = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), - ] - - stances = [] - for stance_name in stance_names: - stance_api_ref = f"engine.util.game_entity_stance.type.{stance_name}" - - stance_ref = f"{game_entity_name}.GameEntityStance.{stance_name}" - stance_raw_api_object = RawAPIObject(stance_ref, stance_name, dataset.nyan_api_objects) - stance_raw_api_object.add_raw_parent(stance_api_ref) - stance_location = ForwardRef(line, ability_ref) - stance_raw_api_object.set_location(stance_location) - - # Search range - stance_raw_api_object.add_raw_member("search_range", - search_range, - "engine.util.game_entity_stance.GameEntityStance") - - # Ability preferences - stance_raw_api_object.add_raw_member("ability_preference", - ability_preferences, - "engine.util.game_entity_stance.GameEntityStance") - - # Type preferences - stance_raw_api_object.add_raw_member("type_preference", - type_preferences, - "engine.util.game_entity_stance.GameEntityStance") - - line.add_raw_api_object(stance_raw_api_object) - stance_forward_ref = ForwardRef(line, stance_ref) - stances.append(stance_forward_ref) - - ability_raw_api_object.add_raw_member("stances", - stances, - "engine.ability.type.GameEntityStance") - - 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 formation_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Formation 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}.Formation" - ability_raw_api_object = RawAPIObject(ability_ref, "Formation", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Formation") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Formation definitions - if line.get_class_id() in (6,): - subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Infantry"].get_nyan_object( - ) - - elif line.get_class_id() in (12, 47): - subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Cavalry"].get_nyan_object( - ) - - elif line.get_class_id() in (0, 23, 36, 44, 55): - subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Ranged"].get_nyan_object( - ) - - elif line.get_class_id() in (2, 13, 18, 20, 35, 43, 51, 59): - subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Siege"].get_nyan_object( - ) - - else: - subformation = dataset.pregen_nyan_objects["util.formation.subformation.types.Support"].get_nyan_object( - ) - - formation_names = ["Line", "Staggered", "Box", "Flank"] - - formation_defs = [] - for formation_name in formation_names: - ge_formation_ref = f"{game_entity_name}.Formation.{formation_name}" - ge_formation_raw_api_object = RawAPIObject(ge_formation_ref, - formation_name, - dataset.nyan_api_objects) - ge_formation_raw_api_object.add_raw_parent( - "engine.util.game_entity_formation.GameEntityFormation") - ge_formation_location = ForwardRef(line, ability_ref) - ge_formation_raw_api_object.set_location(ge_formation_location) - - # Formation - formation_ref = f"util.formation.types.{formation_name}" - formation = dataset.pregen_nyan_objects[formation_ref].get_nyan_object() - ge_formation_raw_api_object.add_raw_member("formation", - formation, - "engine.util.game_entity_formation.GameEntityFormation") - - # Subformation - ge_formation_raw_api_object.add_raw_member("subformation", - subformation, - "engine.util.game_entity_formation.GameEntityFormation") - - line.add_raw_api_object(ge_formation_raw_api_object) - ge_formation_forward_ref = ForwardRef(line, ge_formation_ref) - formation_defs.append(ge_formation_forward_ref) - - ability_raw_api_object.add_raw_member("formations", - formation_defs, - "engine.ability.type.Formation") - - 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 foundation_ability(line: GenieGameEntityGroup, terrain_id: int = -1) -> ForwardRef: - """ - Adds the Foundation abilities to a line. Optionally chooses the specified - terrain ID. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param terrain_id: Force this terrain ID as foundation - :type terrain_id: int - :returns: The forward references for the abilities. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.Foundation" - ability_raw_api_object = RawAPIObject(ability_ref, "Foundation", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Foundation") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Terrain - if terrain_id == -1: - terrain_id = current_unit["foundation_terrain_id"].value - - terrain = dataset.terrain_groups[terrain_id] - terrain_forward_ref = ForwardRef(terrain, terrain_lookup_dict[terrain_id][1]) - ability_raw_api_object.add_raw_member("foundation_terrain", - terrain_forward_ref, - "engine.ability.type.Foundation") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def gather_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Gather abilities to a line. Unlike the other methods, this - creates multiple abilities. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward references for the abilities. - :rtype: list - """ - if isinstance(line, GenieVillagerGroup): - gatherers = line.variants[0].line - - else: - gatherers = [line.line[0]] - - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - abilities = [] - for gatherer in gatherers: - unit_commands = gatherer["unit_commands"].value - resource = None - ability_animation_id = -1 - harvestable_class_ids = OrderedSet() - harvestable_unit_ids = OrderedSet() - - for command in unit_commands: - # Find a gather ability. It doesn't matter which one because - # they should all produce the same resource for one genie unit. - type_id = command["type"].value - - if type_id not in (5, 110): - continue - - target_class_id = command["class_id"].value - if target_class_id > -1: - harvestable_class_ids.add(target_class_id) - - target_unit_id = command["unit_id"].value - if target_unit_id > -1: - harvestable_unit_ids.add(target_unit_id) - - resource_id = command["resource_out"].value - - # If resource_out is not specified, the gatherer harvests resource_in - if resource_id == -1: - resource_id = command["resource_in"].value - - if resource_id == 0: - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object( - ) - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object( - ) - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( - ) - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object( - ) - - else: - continue - - if type_id == 110: - ability_animation_id = command["work_sprite_id"].value - - else: - ability_animation_id = command["proceed_sprite_id"].value - - # Look for the harvestable groups that match the class IDs and unit IDs - check_groups = [] - check_groups.extend(dataset.unit_lines.values()) - check_groups.extend(dataset.building_lines.values()) - check_groups.extend(dataset.ambient_groups.values()) - - harvestable_groups = [] - for group in check_groups: - if not group.is_harvestable(): - continue - - if group.get_class_id() in harvestable_class_ids: - harvestable_groups.append(group) - continue - - for unit_id in harvestable_unit_ids: - if group.contains_entity(unit_id): - harvestable_groups.append(group) - - if len(harvestable_groups) == 0: - # If no matching groups are found, then we don't - # need to create an ability. - continue - - gatherer_unit_id = gatherer.get_id() - if gatherer_unit_id not in gather_lookup_dict: - # Skips hunting wolves - continue - - ability_name = gather_lookup_dict[gatherer_unit_id][0] - - 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("engine.ability.type.Gather") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Ability properties - properties = {} - - # Animation - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - ability_animation_id, - property_ref, - ability_name, - f"{gather_lookup_dict[gatherer_unit_id][1]}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [ - dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Auto resume - ability_raw_api_object.add_raw_member("auto_resume", - True, - "engine.ability.type.Gather") - - # search range - ability_raw_api_object.add_raw_member("resume_search_range", - MemberSpecialValue.NYAN_INF, - "engine.ability.type.Gather") - - # Gather rate - rate_name = f"{game_entity_name}.{ability_name}.GatherRate" - rate_raw_api_object = RawAPIObject(rate_name, "GatherRate", dataset.nyan_api_objects) - rate_raw_api_object.add_raw_parent("engine.util.resource.ResourceRate") - rate_location = ForwardRef(line, ability_ref) - rate_raw_api_object.set_location(rate_location) - - rate_raw_api_object.add_raw_member( - "type", resource, "engine.util.resource.ResourceRate") - - gather_rate = gatherer["work_rate"].value - rate_raw_api_object.add_raw_member( - "rate", gather_rate, "engine.util.resource.ResourceRate") - - line.add_raw_api_object(rate_raw_api_object) - - rate_forward_ref = ForwardRef(line, rate_name) - ability_raw_api_object.add_raw_member("gather_rate", - rate_forward_ref, - "engine.ability.type.Gather") - - # Resource container - container_ref = (f"{game_entity_name}.ResourceStorage." - f"{gather_lookup_dict[gatherer_unit_id][0]}Container") - container_forward_ref = ForwardRef(line, container_ref) - ability_raw_api_object.add_raw_member("container", - container_forward_ref, - "engine.ability.type.Gather") - - # Targets (resource spots) - entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) - spot_forward_refs = [] - for group in harvestable_groups: - group_id = group.get_head_unit_id() - group_name = entity_lookups[group_id][0] - - spot_forward_ref = ForwardRef(group, - f"{group_name}.Harvestable.{group_name}ResourceSpot") - spot_forward_refs.append(spot_forward_ref) - - ability_raw_api_object.add_raw_member("targets", - spot_forward_refs, - "engine.ability.type.Gather") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - abilities.append(ability_forward_ref) - - return abilities - - @staticmethod - def harvestable_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Harvestable 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - 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}.Harvestable" - ability_raw_api_object = RawAPIObject(ability_ref, "Harvestable", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Harvestable") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Resource spot - resource_storage = current_unit["resource_storage"].value - - for storage in resource_storage: - resource_id = storage["type"].value - - # IDs 15, 16, 17 are other types of food (meat, berries, fish) - if resource_id in (0, 15, 16, 17): - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( - ) - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() - - else: - continue - - spot_name = f"{game_entity_name}.Harvestable.{game_entity_name}ResourceSpot" - spot_raw_api_object = RawAPIObject(spot_name, - f"{game_entity_name}ResourceSpot", - dataset.nyan_api_objects) - spot_raw_api_object.add_raw_parent("engine.util.resource_spot.ResourceSpot") - spot_location = ForwardRef(line, ability_ref) - spot_raw_api_object.set_location(spot_location) - - # Type - spot_raw_api_object.add_raw_member("resource", - resource, - "engine.util.resource_spot.ResourceSpot") - - # Start amount (equals max amount) - if line.get_id() == 50: - # Farm food amount (hardcoded in civ) - starting_amount = dataset.genie_civs[1]["resources"][36].value - - elif line.get_id() == 199: - # Fish trap food amount (hardcoded in civ) - starting_amount = storage["amount"].value - starting_amount += dataset.genie_civs[1]["resources"][88].value - - else: - starting_amount = storage["amount"].value - - spot_raw_api_object.add_raw_member("starting_amount", - starting_amount, - "engine.util.resource_spot.ResourceSpot") - - # Max amount - spot_raw_api_object.add_raw_member("max_amount", - starting_amount, - "engine.util.resource_spot.ResourceSpot") - - # Decay rate - decay_rate = current_unit["resource_decay"].value - spot_raw_api_object.add_raw_member("decay_rate", - decay_rate, - "engine.util.resource_spot.ResourceSpot") - - spot_forward_ref = ForwardRef(line, spot_name) - ability_raw_api_object.add_raw_member("resources", - spot_forward_ref, - "engine.ability.type.Harvestable") - line.add_raw_api_object(spot_raw_api_object) - - # Only one resource spot per ability - break - - # Harvest Progress (we don't use this for Aoe2) - ability_raw_api_object.add_raw_member("harvest_progress", - [], - "engine.ability.type.Harvestable") - - # Restock Progress - progress_forward_refs = [] - if line.get_class_id() == 49: - # Farms - # ===================================================================================== - progress_ref = f"{ability_ref}.RestockProgress33" - progress_raw_api_object = RawAPIObject(progress_ref, - "RestockProgress33", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Restock"], - "engine.util.progress.Progress") - - # Interval = (0.0, 33.0) - progress_raw_api_object.add_raw_member("left_boundary", - 0.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 33.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction1" - terrain_group = dataset.terrain_groups[29] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - init_state_ref = f"{game_entity_name}.Constructable.InitState" - init_state_forward_ref = ForwardRef(line, init_state_ref) - property_raw_api_object.add_raw_member("state_change", - init_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.RestockProgress66" - progress_raw_api_object = RawAPIObject(progress_ref, - "RestockProgress66", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Restock"], - "engine.util.progress.Progress") - - # Interval = (33.0, 66.0) - progress_raw_api_object.add_raw_member("left_boundary", - 33.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 66.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction2" - terrain_group = dataset.terrain_groups[30] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - construct_state_ref = f"{game_entity_name}.Constructable.ConstructState" - construct_state_forward_ref = ForwardRef(line, construct_state_ref) - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.RestockProgress100" - progress_raw_api_object = RawAPIObject(progress_ref, - "RestockProgress100", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Restock"], - "engine.util.progress.Progress") - - progress_raw_api_object.add_raw_member("left_boundary", - 66.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 100.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction3" - terrain_group = dataset.terrain_groups[31] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - construct_state_ref = f"{game_entity_name}.Constructable.ConstructState" - construct_state_forward_ref = ForwardRef(line, construct_state_ref) - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ======================================================================= - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - - ability_raw_api_object.add_raw_member("restock_progress", - progress_forward_refs, - "engine.ability.type.Harvestable") - - # Gatherer limit (infinite in AoC except for farms) - gatherer_limit = MemberSpecialValue.NYAN_INF - if line.get_class_id() == 49: - gatherer_limit = 1 - - ability_raw_api_object.add_raw_member("gatherer_limit", - gatherer_limit, - "engine.ability.type.Harvestable") - - # Unit have to die before they are harvestable (except for farms) - harvestable_by_default = current_unit["hit_points"].value == 0 - if line.get_class_id() == 49: - harvestable_by_default = True - - ability_raw_api_object.add_raw_member("harvestable_by_default", - harvestable_by_default, - "engine.ability.type.Harvestable") - - 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 herd_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Herd 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}.Herd" - ability_raw_api_object = RawAPIObject(ability_ref, "Herd", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Herd") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Strength - ability_raw_api_object.add_raw_member("strength", - 0, - "engine.ability.type.Herd") - - # Allowed types - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Herdable"].get_nyan_object() - ] - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.Herd") - - # Blacklisted entities - ability_raw_api_object.add_raw_member("blacklisted_entities", - [], - "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 - def herdable_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Herdable 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}.Herdable" - ability_raw_api_object = RawAPIObject(ability_ref, - "Herdable", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Herdable") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Mode - mode = dataset.nyan_api_objects["engine.util.herdable_mode.type.LongestTimeInRange"] - ability_raw_api_object.add_raw_member("mode", mode, "engine.ability.type.Herdable") - - # Discover range - ability_raw_api_object.add_raw_member("adjacent_discover_range", - 1.0, - "engine.ability.type.Herdable") - - 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 idle_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Idle 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.Idle" - ability_raw_api_object = RawAPIObject(ability_ref, "Idle", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Idle") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Ability properties - properties = {} - - # Animation - ability_animation_id = current_unit["idle_graphic0"].value - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - ability_animation_id, - property_ref, - "Idle", - "idle_") - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", - animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Create custom civ graphics - handled_graphics_set_ids = set() - for civ_group in dataset.civ_groups.values(): - civ = civ_group.civ - civ_id = civ_group.get_id() - - # Only proceed if the civ stores the unit in the line - if current_unit_id not in civ["units"].value.keys(): - continue - - civ_animation_id = civ["units"][current_unit_id]["idle_graphic0"].value - - if civ_animation_id != ability_animation_id: - # Find the corresponding graphics set - for set_id, items in gset_lookup_dict.items(): - if civ_id in items[0]: - graphics_set_id = set_id - break - - else: - raise RuntimeError(f"No graphics set found for civ id {civ_id}") - - # Check if the object for the animation has been created before - obj_exists = graphics_set_id in handled_graphics_set_ids - if not obj_exists: - handled_graphics_set_ids.add(graphics_set_id) - - obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}Idle" - filename_prefix = f"idle_{gset_lookup_dict[graphics_set_id][2]}_" - AoCAbilitySubprocessor.create_civ_animation(line, - civ_group, - civ_animation_id, - property_ref, - obj_prefix, - filename_prefix, - obj_exists) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def live_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Live 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 = line.get_head_unit() - 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}.Live" - ability_raw_api_object = RawAPIObject(ability_ref, "Live", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Live") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - attributes_set = [] - - # Health - # ======================================================================================= - health_ref = f"{game_entity_name}.Live.Health" - health_raw_api_object = RawAPIObject(health_ref, "Health", dataset.nyan_api_objects) - health_raw_api_object.add_raw_parent("engine.util.attribute.AttributeSetting") - health_location = ForwardRef(line, ability_ref) - health_raw_api_object.set_location(health_location) - - attribute_value = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object( - ) - health_raw_api_object.add_raw_member("attribute", - attribute_value, - "engine.util.attribute.AttributeSetting") - - # Lowest HP can go - health_raw_api_object.add_raw_member("min_value", - 0, - "engine.util.attribute.AttributeSetting") - - # Max HP and starting HP - max_hp_value = current_unit["hit_points"].value - health_raw_api_object.add_raw_member("max_value", - max_hp_value, - "engine.util.attribute.AttributeSetting") - - starting_value = max_hp_value - if isinstance(line, GenieBuildingLineGroup): - # Buildings spawn with 1 HP - starting_value = 1 - - health_raw_api_object.add_raw_member("starting_value", - starting_value, - "engine.util.attribute.AttributeSetting") - - line.add_raw_api_object(health_raw_api_object) - - # ======================================================================================= - health_forward_ref = ForwardRef(line, health_raw_api_object.get_id()) - attributes_set.append(health_forward_ref) - - if current_unit_id == 125: - # Faith (only monk) - faith_ref = f"{game_entity_name}.Live.Faith" - faith_raw_api_object = RawAPIObject(faith_ref, "Faith", dataset.nyan_api_objects) - faith_raw_api_object.add_raw_parent("engine.util.attribute.AttributeSetting") - faith_location = ForwardRef(line, ability_ref) - faith_raw_api_object.set_location(faith_location) - - attribute_value = dataset.pregen_nyan_objects["util.attribute.types.Faith"].get_nyan_object( - ) - faith_raw_api_object.add_raw_member("attribute", attribute_value, - "engine.util.attribute.AttributeSetting") - - # Lowest faith can go - faith_raw_api_object.add_raw_member("min_value", - 0, - "engine.util.attribute.AttributeSetting") - - # Max faith and starting faith - faith_raw_api_object.add_raw_member("max_value", - 100, - "engine.util.attribute.AttributeSetting") - faith_raw_api_object.add_raw_member("starting_value", - 100, - "engine.util.attribute.AttributeSetting") - - line.add_raw_api_object(faith_raw_api_object) - - faith_forward_ref = ForwardRef(line, faith_ref) - attributes_set.append(faith_forward_ref) - - ability_raw_api_object.add_raw_member("attributes", attributes_set, - "engine.ability.type.Live") - - 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 los_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the LineOfSight 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - 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}.LineOfSight" - ability_raw_api_object = RawAPIObject(ability_ref, "LineOfSight", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.LineOfSight") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Line of sight - line_of_sight = current_unit["line_of_sight"].value - ability_raw_api_object.add_raw_member("range", line_of_sight, - "engine.ability.type.LineOfSight") - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties = { - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - } - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def move_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Move 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.Move" - ability_raw_api_object = RawAPIObject(ability_ref, "Move", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Move") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Ability properties - properties = {} - - # Animation - ability_animation_id = current_unit["move_graphics"].value - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - - animation_obj_prefix = "Move" - animation_filename_prefix = "move_" - - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - ability_animation_id, - property_ref, - animation_obj_prefix, - animation_filename_prefix) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", - animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Create custom civ graphics - handled_graphics_set_ids = set() - for civ_group in dataset.civ_groups.values(): - civ = civ_group.civ - civ_id = civ_group.get_id() - - # Only proceed if the civ stores the unit in the line - if current_unit_id not in civ["units"].value.keys(): - continue - - civ_animation_id = civ["units"][current_unit_id]["move_graphics"].value - - if civ_animation_id != ability_animation_id: - # Find the corresponding graphics set - graphics_set_id = -1 - for set_id, items in gset_lookup_dict.items(): - if civ_id in items[0]: - graphics_set_id = set_id - break - - # Check if the object for the animation has been created before - obj_exists = graphics_set_id in handled_graphics_set_ids - if not obj_exists: - handled_graphics_set_ids.add(graphics_set_id) - - obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}Move" - filename_prefix = f"move_{gset_lookup_dict[graphics_set_id][2]}_" - AoCAbilitySubprocessor.create_civ_animation(line, - civ_group, - civ_animation_id, - property_ref, - obj_prefix, - filename_prefix, - obj_exists) - - # Command Sound - ability_comm_sound_id = current_unit["command_sound_id"].value - if ability_comm_sound_id > -1: - property_ref = f"{ability_ref}.CommandSound" - property_raw_api_object = RawAPIObject(property_ref, - "CommandSound", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - sounds_set = [] - - sound_obj_prefix = "Move" - - sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, - ability_comm_sound_id, - property_ref, - sound_obj_prefix, - "command_") - sounds_set.append(sound_forward_ref) - property_raw_api_object.add_raw_member("sounds", sounds_set, - "engine.ability.property.type.CommandSound") - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Speed - speed = current_unit["speed"].value - ability_raw_api_object.add_raw_member("speed", speed, "engine.ability.type.Move") - - # Standard move modes - move_modes = [ - dataset.nyan_api_objects["engine.util.move_mode.type.AttackMove"], - dataset.nyan_api_objects["engine.util.move_mode.type.Normal"], - dataset.nyan_api_objects["engine.util.move_mode.type.Patrol"] - ] - - # Follow - ability_ref = f"{game_entity_name}.Move.Follow" - follow_raw_api_object = RawAPIObject(ability_ref, "Follow", dataset.nyan_api_objects) - follow_raw_api_object.add_raw_parent("engine.util.move_mode.type.Follow") - follow_location = ForwardRef(line, f"{game_entity_name}.Move") - follow_raw_api_object.set_location(follow_location) - - follow_range = current_unit["line_of_sight"].value - 1 - follow_raw_api_object.add_raw_member("range", - follow_range, - "engine.util.move_mode.type.Follow") - - line.add_raw_api_object(follow_raw_api_object) - follow_forward_ref = ForwardRef(line, follow_raw_api_object.get_id()) - move_modes.append(follow_forward_ref) - - ability_raw_api_object.add_raw_member("modes", move_modes, "engine.ability.type.Move") - - # Path type - path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() - restrictions = current_unit["terrain_restriction"].value - if restrictions in (0x00, 0x0C, 0x0E, 0x17): - # air units - path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() - - elif restrictions in (0x03, 0x0D, 0x0F): - # ships - path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() - - ability_raw_api_object.add_raw_member("path_type", path_type, "engine.ability.type.Move") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def move_projectile_ability(line: GenieGameEntityGroup, position: int = -1) -> ForwardRef: - """ - Adds the Move ability to a projectile of the specified 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 - """ - dataset = line.data - api_objects = dataset.nyan_api_objects - - if position == 0: - current_unit_id = line.get_head_unit_id() - projectile_id = line.get_head_unit()["projectile_id0"].value - current_unit = dataset.genie_units[projectile_id] - - elif position == 1: - current_unit_id = line.get_head_unit_id() - projectile_id = line.get_head_unit()["projectile_id1"].value - current_unit = dataset.genie_units[projectile_id] - - else: - raise ValueError(f"Invalid projectile number: {position}") - - 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"Projectile{position}.Move" - ability_raw_api_object = RawAPIObject(ability_ref, "Move", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Move") - ability_location = ForwardRef(line, - f"{game_entity_name}.ShootProjectile.Projectile{position}") - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Ability properties - properties = {} - - # Animation - ability_animation_id = current_unit["move_graphics"].value - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_obj_prefix = "ProjectileFly" - animation_filename_prefix = "projectile_fly_" - - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - ability_animation_id, - property_ref, - animation_obj_prefix, - animation_filename_prefix) - - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", - animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Speed - speed = current_unit["speed"].value - ability_raw_api_object.add_raw_member("speed", speed, "engine.ability.type.Move") - - # Move modes - move_modes = [ - dataset.nyan_api_objects["engine.util.move_mode.type.Normal"], - ] - ability_raw_api_object.add_raw_member("modes", move_modes, "engine.ability.type.Move") - - # Path type - path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() - ability_raw_api_object.add_raw_member("path_type", path_type, "engine.ability.type.Move") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def named_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Named 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 = line.get_head_unit() - 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}.Named" - ability_raw_api_object = RawAPIObject(ability_ref, "Named", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Named") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Name - name_ref = f"{game_entity_name}.Named.{game_entity_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{game_entity_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(line, ability_ref) - name_raw_api_object.set_location(name_location) - - name_string_id = current_unit["language_dll_name"].value - translations = AoCAbilitySubprocessor.create_language_strings(line, - name_string_id, - name_ref, - f"{game_entity_name}Name") - name_raw_api_object.add_raw_member("translations", - translations, - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(line, name_ref) - ability_raw_api_object.add_raw_member("name", name_forward_ref, "engine.ability.type.Named") - line.add_raw_api_object(name_raw_api_object) - - # Description - description_ref = f"{game_entity_name}.Named.{game_entity_name}Description" - description_raw_api_object = RawAPIObject(description_ref, - f"{game_entity_name}Description", - dataset.nyan_api_objects) - description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - description_location = ForwardRef(line, ability_ref) - description_raw_api_object.set_location(description_location) - - description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - description_forward_ref = ForwardRef(line, description_ref) - ability_raw_api_object.add_raw_member("description", - description_forward_ref, - "engine.ability.type.Named") - line.add_raw_api_object(description_raw_api_object) - - # Long description - long_description_ref = f"{game_entity_name}.Named.{game_entity_name}LongDescription" - long_description_raw_api_object = RawAPIObject(long_description_ref, - f"{game_entity_name}LongDescription", - dataset.nyan_api_objects) - long_description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - long_description_location = ForwardRef(line, ability_ref) - long_description_raw_api_object.set_location(long_description_location) - - long_description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - long_description_forward_ref = ForwardRef(line, long_description_ref) - ability_raw_api_object.add_raw_member("long_description", - long_description_forward_ref, - "engine.ability.type.Named") - line.add_raw_api_object(long_description_raw_api_object) - - 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 overlay_terrain_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the OverlayTerrain to a line. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward references for the abilities. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.OverlayTerrain" - ability_raw_api_object = RawAPIObject(ability_ref, - "OverlayTerrain", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.OverlayTerrain") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Terrain (Use foundation terrain) - terrain_id = current_unit["foundation_terrain_id"].value - terrain = dataset.terrain_groups[terrain_id] - terrain_forward_ref = ForwardRef(terrain, terrain_lookup_dict[terrain_id][1]) - ability_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.ability.type.OverlayTerrain") - - 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 pathable_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Pathable 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}.Pathable" - ability_raw_api_object = RawAPIObject(ability_ref, - "Pathable", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Pathable") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Hitbox - hitbox_ref = f"{game_entity_name}.Collision.{game_entity_name}Hitbox" - hitbox_forward_ref = ForwardRef(line, hitbox_ref) - ability_raw_api_object.add_raw_member("hitbox", - hitbox_forward_ref, - "engine.ability.type.Pathable") - - # Costs - path_costs = { - dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object(): 255, # impassable - dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object(): 255, # impassable - } - ability_raw_api_object.add_raw_member("path_costs", - path_costs, - "engine.ability.type.Pathable") - - 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 production_queue_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ProductionQueue 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}.ProductionQueue" - ability_raw_api_object = RawAPIObject(ability_ref, - "ProductionQueue", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.ProductionQueue") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Size - size = 14 - - ability_raw_api_object.add_raw_member("size", - size, - "engine.ability.type.ProductionQueue") - - # Production modes - modes = [] - - mode_name = f"{game_entity_name}.ProvideContingent.CreatablesMode" - mode_raw_api_object = RawAPIObject(mode_name, "CreatablesMode", dataset.nyan_api_objects) - mode_raw_api_object.add_raw_parent("engine.util.production_mode.type.Creatables") - mode_location = ForwardRef(line, ability_ref) - mode_raw_api_object.set_location(mode_location) - - # AoE2 allows all creatables in production queue - mode_raw_api_object.add_raw_member("exclude", - [], - "engine.util.production_mode.type.Creatables") - - mode_forward_ref = ForwardRef(line, mode_name) - modes.append(mode_forward_ref) - - ability_raw_api_object.add_raw_member("production_modes", - modes, - "engine.ability.type.ProductionQueue") - - line.add_raw_api_object(mode_raw_api_object) - 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 projectile_ability(line: GenieGameEntityGroup, position: int = 0) -> ForwardRef: - """ - Adds a Projectile ability to projectiles in a line. Which projectile should - be added is determined by the 'position' argument. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param position: When 0, gives the first projectile its ability. When 1, the second... - :type position: int - :returns: The forward reference for the ability. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - current_unit = line.get_head_unit() - 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] - - # First projectile is mandatory - obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{str(position)}" - ability_ref = f"{game_entity_name}.ShootProjectile.Projectile{position}.Projectile" - ability_raw_api_object = RawAPIObject(ability_ref, - "Projectile", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Projectile") - ability_location = ForwardRef(line, obj_ref) - ability_raw_api_object.set_location(ability_location) - - # Arc - if position == 0: - projectile_id = current_unit["projectile_id0"].value - - elif position == 1: - projectile_id = current_unit["projectile_id1"].value - - else: - raise ValueError(f"Invalid projectile position {position}") - - projectile = dataset.genie_units[projectile_id] - arc = degrees(projectile["projectile_arc"].value) - ability_raw_api_object.add_raw_member("arc", - arc, - "engine.ability.type.Projectile") - - # Accuracy - accuracy_name = (f"{game_entity_name}.ShootProjectile." - f"Projectile{position}.Projectile.Accuracy") - accuracy_raw_api_object = RawAPIObject(accuracy_name, - "Accuracy", - dataset.nyan_api_objects) - accuracy_raw_api_object.add_raw_parent("engine.util.accuracy.Accuracy") - accuracy_location = ForwardRef(line, ability_ref) - accuracy_raw_api_object.set_location(accuracy_location) - - accuracy_value = current_unit["accuracy"].value - accuracy_raw_api_object.add_raw_member("accuracy", - accuracy_value, - "engine.util.accuracy.Accuracy") - - accuracy_dispersion = current_unit["accuracy_dispersion"].value - accuracy_raw_api_object.add_raw_member("accuracy_dispersion", - accuracy_dispersion, - "engine.util.accuracy.Accuracy") - dropoff_type = dataset.nyan_api_objects["engine.util.dropoff_type.type.InverseLinear"] - accuracy_raw_api_object.add_raw_member("dispersion_dropoff", - dropoff_type, - "engine.util.accuracy.Accuracy") - - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() - ] - accuracy_raw_api_object.add_raw_member("target_types", - allowed_types, - "engine.util.accuracy.Accuracy") - accuracy_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.util.accuracy.Accuracy") - - line.add_raw_api_object(accuracy_raw_api_object) - accuracy_forward_ref = ForwardRef(line, accuracy_name) - ability_raw_api_object.add_raw_member("accuracy", - [accuracy_forward_ref], - "engine.ability.type.Projectile") - - # Target mode - target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.CurrentPosition"] - ability_raw_api_object.add_raw_member("target_mode", - target_mode, - "engine.ability.type.Projectile") - - # Ingore types; buildings are ignored unless targeted - ignore_forward_refs = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object() - ] - ability_raw_api_object.add_raw_member("ignored_types", - ignore_forward_refs, - "engine.ability.type.Projectile") - ability_raw_api_object.add_raw_member("unignored_entities", - [], - "engine.ability.type.Projectile") - - 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 provide_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ProvideContingent 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 = line.get_head_unit() - if isinstance(line, GenieStackBuildingGroup): - current_unit = line.get_stack_unit() - - 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}.ProvideContingent" - - # Stores the pop space - resource_storage = current_unit["resource_storage"].value - - contingents = [] - for storage in resource_storage: - type_id = storage["type"].value - - if type_id == 4: - resource = dataset.pregen_nyan_objects["util.resource.types.PopulationSpace"].get_nyan_object( - ) - resource_name = "PopSpace" - - else: - continue - - amount = storage["amount"].value - - contingent_amount_name = f"{game_entity_name}.ProvideContingent.{resource_name}" - contingent_amount = RawAPIObject(contingent_amount_name, resource_name, - dataset.nyan_api_objects) - contingent_amount.add_raw_parent("engine.util.resource.ResourceAmount") - ability_forward_ref = ForwardRef(line, ability_ref) - contingent_amount.set_location(ability_forward_ref) - - contingent_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - contingent_amount.add_raw_member("amount", - amount, - "engine.util.resource.ResourceAmount") - - line.add_raw_api_object(contingent_amount) - contingent_amount_forward_ref = ForwardRef(line, - contingent_amount_name) - contingents.append(contingent_amount_forward_ref) - - if not contingents: - # Do not create the ability if the unit provides no contingents - return None - - ability_raw_api_object = RawAPIObject(ability_ref, - "ProvideContingent", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.ProvideContingent") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - ability_raw_api_object.add_raw_member("amount", - contingents, - "engine.ability.type.ProvideContingent") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def rally_point_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the RallyPoint 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}.RallyPoint" - ability_raw_api_object = RawAPIObject(ability_ref, "RallyPoint", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.RallyPoint") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - 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 regenerate_attribute_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the RegenerateAttribute ability to a line. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward references for the ability. - :rtype: list - """ - current_unit_id = line.get_head_unit_id() - dataset = line.data - - attribute = None - attribute_name = "" - if current_unit_id == 125: - # Monk; regenerates Faith - attribute = dataset.pregen_nyan_objects["util.attribute.types.Faith"].get_nyan_object() - attribute_name = "Faith" - - elif current_unit_id == 692: - # Berserk: regenerates Health - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - attribute_name = "Health" - - else: - return [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_name = f"Regenerate{attribute_name}" - 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("engine.ability.type.RegenerateAttribute") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Attribute rate - # =============================================================================== - rate_name = f"{attribute_name}Rate" - rate_ref = f"{game_entity_name}.{ability_name}.{rate_name}" - rate_raw_api_object = RawAPIObject(rate_ref, rate_name, dataset.nyan_api_objects) - rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") - rate_location = ForwardRef(line, ability_ref) - rate_raw_api_object.set_location(rate_location) - - # Attribute - rate_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeRate") - - # Rate - attribute_rate = 0 - if current_unit_id == 125: - # stored in civ resources - attribute_rate = dataset.genie_civs[0]["resources"][35].value - - elif current_unit_id == 692: - # stored in civ resources, but has to get converted to amount/second - heal_timer = dataset.genie_civs[0]["resources"][96].value - attribute_rate = 1 / heal_timer - - rate_raw_api_object.add_raw_member("rate", - attribute_rate, - "engine.util.attribute.AttributeRate") - - line.add_raw_api_object(rate_raw_api_object) - # =============================================================================== - rate_forward_ref = ForwardRef(line, rate_ref) - ability_raw_api_object.add_raw_member("rate", - rate_forward_ref, - "engine.ability.type.RegenerateAttribute") - - 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 regenerate_resource_spot_ability(line: GenieGameEntityGroup) -> None: - """ - Adds the RegenerateResourceSpot 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 - """ - # Unused in AoC - - @staticmethod - def remove_storage_ability(line) -> ForwardRef: - """ - Adds the RemoveStorage 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}.RemoveStorage" - ability_raw_api_object = RawAPIObject(ability_ref, - "RemoveStorage", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.RemoveStorage") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Container - container_ref = f"{game_entity_name}.Storage.{game_entity_name}Container" - container_forward_ref = ForwardRef(line, container_ref) - ability_raw_api_object.add_raw_member("container", - container_forward_ref, - "engine.ability.type.RemoveStorage") - - # Storage elements - elements = [] - entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) - for entity in line.garrison_entities: - entity_ref = entity_lookups[entity.get_head_unit_id()][0] - entity_forward_ref = ForwardRef(entity, entity_ref) - elements.append(entity_forward_ref) - - ability_raw_api_object.add_raw_member("storage_elements", - elements, - "engine.ability.type.RemoveStorage") - - 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 restock_ability(line: GenieGameEntityGroup, restock_target_id: int) -> ForwardRef: - """ - Adds the Restock 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 - api_objects = dataset.nyan_api_objects - - # get the restock target - converter_groups = {} - converter_groups.update(dataset.unit_lines) - converter_groups.update(dataset.building_lines) - converter_groups.update(dataset.ambient_groups) - - restock_target = converter_groups[restock_target_id] - - if not restock_target.is_harvestable(): - raise RuntimeError(f"{restock_target} cannot be restocked: is not harvestable") - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - restock_lookup_dict = internal_name_lookups.get_restock_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - ability_ref = f"{game_entity_name}.{restock_lookup_dict[restock_target_id][0]}" - ability_raw_api_object = RawAPIObject(ability_ref, - restock_lookup_dict[restock_target_id][0], - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Restock") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Ability properties - properties = {} - - ability_animation_id = -1 - if isinstance(line, GenieVillagerGroup) and restock_target_id == 50: - # Search for the build graphic of farms - restock_unit = line.get_units_with_command(101)[0] - commands = restock_unit["unit_commands"].value - for command in commands: - type_id = command["type"].value - - if type_id == 101: - ability_animation_id = command["work_sprite_id"].value - - if ability_animation_id > -1: - # Make the ability animated - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - ability_animation_id, - property_ref, - restock_lookup_dict[restock_target_id][0], - f"{restock_lookup_dict[restock_target_id][1]}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", - animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Auto restock - ability_raw_api_object.add_raw_member("auto_restock", - True, # always True since AoC - "engine.ability.type.Restock") - - # Target - restock_target_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - restock_target_name = restock_target_lookup_dict[restock_target_id][0] - spot_forward_ref = ForwardRef(restock_target, - (f"{restock_target_name}.Harvestable." - f"{restock_target_name}ResourceSpot")) - ability_raw_api_object.add_raw_member("target", - spot_forward_ref, - "engine.ability.type.Restock") - - # restock time - restock_time = restock_target.get_head_unit()["creation_time"].value - ability_raw_api_object.add_raw_member("restock_time", - restock_time, - "engine.ability.type.Restock") - - # Manual/Auto Cost - # Link to the same Cost object as Create - cost_forward_ref = ForwardRef(restock_target, - (f"{restock_target_name}.CreatableGameEntity." - f"{restock_target_name}Cost")) - ability_raw_api_object.add_raw_member("manual_cost", - cost_forward_ref, - "engine.ability.type.Restock") - ability_raw_api_object.add_raw_member("auto_cost", - cost_forward_ref, - "engine.ability.type.Restock") - - # Amount - restock_amount = restock_target.get_head_unit()["resource_capacity"].value - if restock_target_id == 50: - # Farm food amount (hardcoded in civ) - restock_amount = dataset.genie_civs[1]["resources"][36].value - - elif restock_target_id == 199: - # Fish trap added food amount (hardcoded in civ) - restock_amount += dataset.genie_civs[1]["resources"][88].value - - ability_raw_api_object.add_raw_member("amount", - restock_amount, - "engine.ability.type.Restock") - - 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 research_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Research 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 - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - ability_ref = f"{game_entity_name}.Research" - ability_raw_api_object = RawAPIObject(ability_ref, - "Research", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Research") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties = { - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - } - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - researchables_set = [] - for researchable in line.researches: - if researchable.is_unique(): - # Skip this because unique techs are handled by civs - continue - - # ResearchableTech objects are created for each unit/building - # line individually to avoid duplicates. We just point to the - # raw API objects here. - researchable_id = researchable.get_id() - researchable_name = tech_lookup_dict[researchable_id][0] - - raw_api_object_ref = f"{researchable_name}.ResearchableTech" - researchable_forward_ref = ForwardRef(researchable, - raw_api_object_ref) - researchables_set.append(researchable_forward_ref) - - ability_raw_api_object.add_raw_member("researchables", researchables_set, - "engine.ability.type.Research") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def resistance_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Resistance 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}.Resistance" - ability_raw_api_object = RawAPIObject(ability_ref, - "Resistance", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Resistance") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Resistances - resistances = [] - resistances.extend(AoCEffectSubprocessor.get_attack_resistances(line, ability_ref)) - if isinstance(line, (GenieUnitLineGroup, GenieBuildingLineGroup)): - resistances.extend(AoCEffectSubprocessor.get_convert_resistances(line, ability_ref)) - - if isinstance(line, GenieUnitLineGroup) and not line.is_repairable(): - resistances.extend(AoCEffectSubprocessor.get_heal_resistances(line, ability_ref)) - - if isinstance(line, GenieBuildingLineGroup): - resistances.extend( - AoCEffectSubprocessor.get_construct_resistances(line, ability_ref)) - - if line.is_repairable(): - resistances.extend(AoCEffectSubprocessor.get_repair_resistances(line, ability_ref)) - - ability_raw_api_object.add_raw_member("resistances", - resistances, - "engine.ability.type.Resistance") - - 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 resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ResourceStorage 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 - """ - if isinstance(line, GenieVillagerGroup): - gatherers = line.variants[0].line - - else: - gatherers = [line.line[0]] - - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.ResourceStorage" - ability_raw_api_object = RawAPIObject(ability_ref, - "ResourceStorage", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.ResourceStorage") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Create containers - containers = [] - for gatherer in gatherers: - unit_commands = gatherer["unit_commands"].value - resource = None - - used_command = None - for command in unit_commands: - # Find a gather ability. It doesn't matter which one because - # they should all produce the same resource for one genie unit. - type_id = command["type"].value - - if type_id not in (5, 110, 111): - continue - - resource_id = command["resource_out"].value - - # If resource_out is not specified, the gatherer harvests resource_in - if resource_id == -1: - resource_id = command["resource_in"].value - - if resource_id == 0: - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object( - ) - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object( - ) - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( - ) - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object( - ) - - elif type_id == 111: - target_id = command["unit_id"].value - if target_id not in dataset.building_lines.keys(): - # Skips the trade workshop trading which is never used - continue - - # Trade goods --> gold - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object( - ) - - else: - continue - - used_command = command - - if not used_command: - # 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: - # Skips hunting wolves - continue - - container_name = f"{gather_lookup_dict[gatherer_unit_id][0]}Container" - - elif used_command["type"].value == 111: - # Trading - container_name = "TradeContainer" - - container_ref = f"{ability_ref}.{container_name}" - container_raw_api_object = RawAPIObject(container_ref, - container_name, - dataset.nyan_api_objects) - container_raw_api_object.add_raw_parent("engine.util.storage.ResourceContainer") - container_location = ForwardRef(line, ability_ref) - container_raw_api_object.set_location(container_location) - - # Resource - container_raw_api_object.add_raw_member("resource", - resource, - "engine.util.storage.ResourceContainer") - - # Carry capacity - carry_capacity = None - if line.is_gatherer(): - carry_capacity = gatherer["resource_capacity"].value - - elif used_command["type"].value == 111: - # No restriction for trading - carry_capacity = MemberSpecialValue.NYAN_INF - - container_raw_api_object.add_raw_member("max_amount", - carry_capacity, - "engine.util.storage.ResourceContainer") - - # Carry progress - carry_progress = [] - carry_move_animation_id = used_command["carry_sprite_id"].value - if carry_move_animation_id > -1: - # ================================================================================= - progress_ref = f"{ability_ref}.{container_name}CarryProgress" - progress_raw_api_object = RawAPIObject(progress_ref, - f"{container_name}CarryProgress", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, container_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Carry"], - "engine.util.progress.Progress") - - # Interval = (20.0, 100.0) - progress_raw_api_object.add_raw_member("left_boundary", - 20.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 100.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ================================================================================= - # Animated property (animation overrides) - # ================================================================================= - property_ref = f"{progress_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.Animated") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - # ================================================================================= - overrides = [] - # ================================================================================= - # Move override - # ================================================================================= - override_ref = f"{property_ref}.MoveOverride" - override_raw_api_object = RawAPIObject(override_ref, - "MoveOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, property_ref) - override_raw_api_object.set_location(override_location) - - line.add_raw_api_object(override_raw_api_object) - - move_forward_ref = ForwardRef(line, f"{game_entity_name}.Move") - override_raw_api_object.add_raw_member("ability", - move_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - carry_move_animation_id, - override_ref, - "Move", - "move_carry_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - override_forward_ref = ForwardRef(line, override_ref) - overrides.append(override_forward_ref) - # ================================================================================= - # TODO: Idle override (stops on last used frame of Move override?) - # ================================================================================= - # ================================================================================= - property_raw_api_object.add_raw_member("overrides", - overrides, - "engine.util.progress.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - - properties.update({ - api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref - }) - # ================================================================================= - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_ref = ForwardRef(line, progress_ref) - carry_progress.append(progress_forward_ref) - - container_raw_api_object.add_raw_member("carry_progress", - carry_progress, - "engine.util.storage.ResourceContainer") - - line.add_raw_api_object(container_raw_api_object) - - container_forward_ref = ForwardRef(line, container_ref) - containers.append(container_forward_ref) - - ability_raw_api_object.add_raw_member("containers", - containers, - "engine.ability.type.ResourceStorage") - - 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 selectable_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds Selectable abilities to a line. Units will get two of these, - one Rectangle box for the Self stance and one MatchToSprite box - for other stances. - - :param line: Unit/Building line that gets the abilities. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward reference for the abilities. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_refs = (f"{game_entity_name}.Selectable",) - ability_names = ("Selectable",) - - if isinstance(line, GenieUnitLineGroup): - ability_refs = (f"{game_entity_name}.SelectableOthers", - f"{game_entity_name}.SelectableSelf") - ability_names = ("SelectableOthers", - "SelectableSelf") - - abilities = [] - - # First box (MatchToSprite) - ability_ref = ability_refs[0] - ability_name = ability_names[0] - - ability_raw_api_object = RawAPIObject(ability_ref, ability_name, dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Selectable") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Selection box - box_ref = dataset.nyan_api_objects["engine.util.selection_box.type.MatchToSprite"] - ability_raw_api_object.add_raw_member("selection_box", - box_ref, - "engine.ability.type.Selectable") - - # Ability properties - properties = {} - - # Diplomacy setting (for units) - if isinstance(line, GenieUnitLineGroup): - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - stances = [ - dataset.pregen_nyan_objects["util.diplomatic_stance.types.Enemy"].get_nyan_object(), - dataset.pregen_nyan_objects["util.diplomatic_stance.types.Neutral"].get_nyan_object( - ), - dataset.pregen_nyan_objects["util.diplomatic_stance.types.Friendly"].get_nyan_object( - ) - ] - property_raw_api_object.add_raw_member("stances", - stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - else: - ability_comm_sound_id = current_unit["selection_sound_id"].value - if ability_comm_sound_id > -1: - property_ref = f"{ability_ref}.CommandSound" - property_raw_api_object = RawAPIObject(property_ref, - "CommandSound", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - sounds_set = [] - sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, - ability_comm_sound_id, - property_ref, - ability_name, - "command_") - sounds_set.append(sound_forward_ref) - property_raw_api_object.add_raw_member("sounds", - sounds_set, - "engine.ability.property.type.CommandSound") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref - }) - 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()) - - abilities.append(ability_forward_ref) - - if not isinstance(line, GenieUnitLineGroup): - return abilities - - # Second box (Rectangle) - ability_ref = ability_refs[1] - ability_name = ability_names[1] - - ability_raw_api_object = RawAPIObject(ability_ref, - ability_name, - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Selectable") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Ability properties - properties = {} - - # Command Sound - ability_comm_sound_id = current_unit["selection_sound_id"].value - if ability_comm_sound_id > -1: - property_ref = f"{ability_ref}.CommandSound" - property_raw_api_object = RawAPIObject(property_ref, - "CommandSound", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - sounds_set = [] - sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, - ability_comm_sound_id, - property_ref, - ability_name, - "command_") - sounds_set.append(sound_forward_ref) - property_raw_api_object.add_raw_member("sounds", - sounds_set, - "engine.ability.property.type.CommandSound") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Selection box - box_name = f"{game_entity_name}.SelectableSelf.Rectangle" - box_raw_api_object = RawAPIObject(box_name, "Rectangle", dataset.nyan_api_objects) - box_raw_api_object.add_raw_parent("engine.util.selection_box.type.Rectangle") - box_location = ForwardRef(line, ability_ref) - box_raw_api_object.set_location(box_location) - - width = current_unit["selection_shape_x"].value - box_raw_api_object.add_raw_member("width", - width, - "engine.util.selection_box.type.Rectangle") - - height = current_unit["selection_shape_y"].value - box_raw_api_object.add_raw_member("height", - height, - "engine.util.selection_box.type.Rectangle") - - line.add_raw_api_object(box_raw_api_object) - - box_forward_ref = ForwardRef(line, box_name) - ability_raw_api_object.add_raw_member("selection_box", - box_forward_ref, - "engine.ability.type.Selectable") - - line.add_raw_api_object(ability_raw_api_object) - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - abilities.append(ability_forward_ref) - - return abilities - - @staticmethod - def send_back_to_task_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the SendBackToTask 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}.SendBackToTask" - ability_raw_api_object = RawAPIObject(ability_ref, - "SendBackToTask", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.SendBackToTask") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Only works on villagers - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Villager"].get_nyan_object() - ] - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.SendBackToTask") - ability_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.ability.type.SendBackToTask") - - 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 shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> ForwardRef: - """ - Adds the ShootProjectile 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - - ability_name = command_lookup_dict[command_id][0] - - game_entity_name = name_lookup_dict[current_unit_id][0] - 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("engine.ability.type.ShootProjectile") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # 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: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - ability_animation_id, - property_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", - animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Command Sound - ability_comm_sound_id = current_unit["command_sound_id"].value - if ability_comm_sound_id > -1: - property_ref = f"{ability_ref}.CommandSound" - property_raw_api_object = RawAPIObject(property_ref, - "CommandSound", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - sounds_set = [] - sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, - ability_comm_sound_id, - property_ref, - ability_name, - "command_") - sounds_set.append(sound_forward_ref) - property_raw_api_object.add_raw_member("sounds", - sounds_set, - "engine.ability.property.type.CommandSound") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Projectile - projectiles = [] - projectile_primary = current_unit["projectile_id0"].value - if projectile_primary > -1: - projectiles.append(ForwardRef(line, - f"{game_entity_name}.ShootProjectile.Projectile0")) - - projectile_secondary = current_unit["projectile_id1"].value - if projectile_secondary > -1: - projectiles.append(ForwardRef(line, - f"{game_entity_name}.ShootProjectile.Projectile1")) - - ability_raw_api_object.add_raw_member("projectiles", - projectiles, - "engine.ability.type.ShootProjectile") - - # Projectile count - min_projectiles = current_unit["projectile_min_count"].value - max_projectiles = current_unit["projectile_max_count"].value - - if projectile_primary == -1: - # Special case where only the second projectile is defined (town center) - # The min/max projectile count is lowered by 1 in this case - min_projectiles -= 1 - max_projectiles -= 1 - - elif min_projectiles == 0 and max_projectiles == 0: - # If there's a primary projectile defined, but these values are 0, - # the game still fires a projectile on attack. - min_projectiles += 1 - max_projectiles += 1 - - if current_unit_id == 236: - # Bombard Tower (gets treated like a tower for max projectiles) - max_projectiles = 5 - - ability_raw_api_object.add_raw_member("min_projectiles", - min_projectiles, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("max_projectiles", - max_projectiles, - "engine.ability.type.ShootProjectile") - - # Reload time and delay - reload_time = current_unit["attack_speed"].value - ability_raw_api_object.add_raw_member("reload_time", - reload_time, - "engine.ability.type.ShootProjectile") - - if ability_animation_id > -1: - animation = dataset.genie_graphics[ability_animation_id] - frame_rate = animation.get_frame_rate() - - else: - frame_rate = 0 - - spawn_delay_frames = current_unit["frame_delay"].value - spawn_delay = frame_rate * spawn_delay_frames - ability_raw_api_object.add_raw_member("spawn_delay", - spawn_delay, - "engine.ability.type.ShootProjectile") - - # TODO: Hardcoded? - ability_raw_api_object.add_raw_member("projectile_delay", - 0.1, - "engine.ability.type.ShootProjectile") - - # Turning - if isinstance(line, GenieBuildingLineGroup): - require_turning = False - - else: - require_turning = True - - ability_raw_api_object.add_raw_member("require_turning", - require_turning, - "engine.ability.type.ShootProjectile") - - # Manual Aiming (Mangonel + Trebuchet) - manual_aiming_allowed = line.get_head_unit_id() in (280, 331) - - ability_raw_api_object.add_raw_member("manual_aiming_allowed", - manual_aiming_allowed, - "engine.ability.type.ShootProjectile") - - # Spawning area - spawning_area_offset_x = current_unit["weapon_offset"][0].value - spawning_area_offset_y = current_unit["weapon_offset"][1].value - spawning_area_offset_z = current_unit["weapon_offset"][2].value - - ability_raw_api_object.add_raw_member("spawning_area_offset_x", - spawning_area_offset_x, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("spawning_area_offset_y", - spawning_area_offset_y, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("spawning_area_offset_z", - spawning_area_offset_z, - "engine.ability.type.ShootProjectile") - - spawning_area_width = current_unit["projectile_spawning_area_width"].value - spawning_area_height = current_unit["projectile_spawning_area_length"].value - spawning_area_randomness = current_unit["projectile_spawning_area_randomness"].value - - ability_raw_api_object.add_raw_member("spawning_area_width", - spawning_area_width, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("spawning_area_height", - spawning_area_height, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("spawning_area_randomness", - spawning_area_randomness, - "engine.ability.type.ShootProjectile") - - # Restrictions on targets (only units and buildings allowed) - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() - ] - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.ability.type.ShootProjectile") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def stop_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Stop 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 - api_objects = dataset.nyan_api_objects - - 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}.Stop" - ability_raw_api_object = RawAPIObject(ability_ref, "Stop", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Stop") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - - # Ability properties - properties = { - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - } - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def storage_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Storage 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - 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}.Storage" - ability_raw_api_object = RawAPIObject(ability_ref, "Storage", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Storage") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Container - # ============================================================================== - container_name = f"{game_entity_name}.Storage.{game_entity_name}Container" - container_raw_api_object = RawAPIObject(container_name, - f"{game_entity_name}Container", - dataset.nyan_api_objects) - container_raw_api_object.add_raw_parent("engine.util.storage.EntityContainer") - container_location = ForwardRef(line, ability_ref) - container_raw_api_object.set_location(container_location) - - garrison_mode = line.get_garrison_mode() - - # Allowed types - # TODO: Any should be fine for now, since Enter/Exit abilities limit the stored elements - allowed_types = [dataset.nyan_api_objects["engine.util.game_entity_type.type.Any"]] - - container_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.util.storage.EntityContainer") - - # Blacklisted entities - container_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.util.storage.EntityContainer") - - # Define storage elements - storage_element_defs = [] - if garrison_mode is GenieGarrisonMode.UNIT_GARRISON: - for storage_element in line.garrison_entities: - storage_element_name = name_lookup_dict[storage_element.get_head_unit_id()][0] - storage_def_ref = (f"{game_entity_name}.Storage." - f"{game_entity_name}Container." - f"{storage_element_name}StorageDef") - storage_def_raw_api_object = RawAPIObject(storage_def_ref, - f"{storage_element_name}StorageDef", - dataset.nyan_api_objects) - storage_def_raw_api_object.add_raw_parent( - "engine.util.storage.StorageElementDefinition") - storage_def_location = ForwardRef(line, container_name) - storage_def_raw_api_object.set_location(storage_def_location) - - # Storage element - storage_element_forward_ref = ForwardRef(storage_element, storage_element_name) - storage_def_raw_api_object.add_raw_member("storage_element", - storage_element_forward_ref, - "engine.util.storage.StorageElementDefinition") - - # Elements per slot - storage_def_raw_api_object.add_raw_member("elements_per_slot", - 1, - "engine.util.storage.StorageElementDefinition") - - # Conflicts - storage_def_raw_api_object.add_raw_member("conflicts", - [], - "engine.util.storage.StorageElementDefinition") - - # TODO: State change (optional) -> speed boost - - storage_def_forward_ref = ForwardRef(line, storage_def_ref) - storage_element_defs.append(storage_def_forward_ref) - line.add_raw_api_object(storage_def_raw_api_object) - - container_raw_api_object.add_raw_member("storage_element_defs", - storage_element_defs, - "engine.util.storage.EntityContainer") - - # Container slots - slots = current_unit["garrison_capacity"].value - if garrison_mode is GenieGarrisonMode.MONK: - slots = 1 - - container_raw_api_object.add_raw_member("slots", - slots, - "engine.util.storage.EntityContainer") - - # Carry progress - carry_progress = [] - if garrison_mode is GenieGarrisonMode.MONK and isinstance(line, GenieMonkGroup): - switch_unit = line.get_switch_unit() - carry_idle_animation_id = switch_unit["idle_graphic0"].value - carry_move_animation_id = switch_unit["move_graphics"].value - - progress_ref = f"{ability_ref}.CarryProgress" - progress_raw_api_object = RawAPIObject(progress_ref, - "CarryProgress", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Carry"], - "engine.util.progress.Progress") - - # Interval = (0.0, 100.0) - progress_raw_api_object.add_raw_member("left_boundary", - 0.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 100.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Animated property (animation overrides) - # ===================================================================================== - property_ref = f"{progress_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.Animated") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - # ===================================================================================== - overrides = [] - # Idle override - # ===================================================================================== - override_ref = f"{property_ref}.IdleOverride" - override_raw_api_object = RawAPIObject(override_ref, - "IdleOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, property_ref) - override_raw_api_object.set_location(override_location) - - idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") - override_raw_api_object.add_raw_member("ability", - idle_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - carry_idle_animation_id, - override_ref, - "Idle", - "idle_carry_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - override_forward_ref = ForwardRef(line, override_ref) - overrides.append(override_forward_ref) - line.add_raw_api_object(override_raw_api_object) - # ===================================================================================== - # Move override - # ===================================================================================== - override_ref = f"{property_ref}.MoveOverride" - override_raw_api_object = RawAPIObject(override_ref, - "MoveOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, property_ref) - override_raw_api_object.set_location(override_location) - - idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Move") - override_raw_api_object.add_raw_member("ability", - idle_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - carry_move_animation_id, - override_ref, - "Move", - "move_carry_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - override_forward_ref = ForwardRef(line, override_ref) - overrides.append(override_forward_ref) - line.add_raw_api_object(override_raw_api_object) - # ===================================================================================== - property_raw_api_object.add_raw_member("overrides", - overrides, - "engine.util.progress.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref - }) - - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - # ===================================================================================== - carry_state_name = f"{property_ref}.CarryRelicState" - carry_state_raw_api_object = RawAPIObject(carry_state_name, - "CarryRelicState", - dataset.nyan_api_objects) - carry_state_raw_api_object.add_raw_parent("engine.util.state_machine.StateChanger") - carry_state_location = ForwardRef(line, property_ref) - carry_state_raw_api_object.set_location(carry_state_location) - - # Priority - carry_state_raw_api_object.add_raw_member("priority", - 1, - "engine.util.state_machine.StateChanger") - - # Enabled abilities - carry_state_raw_api_object.add_raw_member("enable_abilities", - [], - "engine.util.state_machine.StateChanger") - - # Disabled abilities - disabled_forward_refs = [] - - if line.has_command(104): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Convert")) - - if line.has_command(105): - disabled_forward_refs.append(ForwardRef(line, - f"{game_entity_name}.Heal")) - - carry_state_raw_api_object.add_raw_member("disable_abilities", - disabled_forward_refs, - "engine.util.state_machine.StateChanger") - - # Enabled modifiers - carry_state_raw_api_object.add_raw_member("enable_modifiers", - [], - "engine.util.state_machine.StateChanger") - - # Disabled modifiers - carry_state_raw_api_object.add_raw_member("disable_modifiers", - [], - "engine.util.state_machine.StateChanger") - - line.add_raw_api_object(carry_state_raw_api_object) - # ===================================================================================== - init_state_forward_ref = ForwardRef(line, carry_state_name) - property_raw_api_object.add_raw_member("state_change", - init_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_ref = ForwardRef(line, progress_ref) - carry_progress.append(progress_forward_ref) - - else: - # Garrison graphics - if current_unit.has_member("garrison_graphic"): - garrison_animation_id = current_unit["garrison_graphic"].value - - else: - garrison_animation_id = -1 - - if garrison_animation_id > -1: - progress_ref = f"{ability_ref}.CarryProgress" - progress_raw_api_object = RawAPIObject(progress_ref, - "CarryProgress", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Carry"], - "engine.util.progress.Progress") - - # Interval = (0.0, 100.0) - progress_raw_api_object.add_raw_member("left_boundary", - 0.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 100.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # Animated property (animation overrides) - # ================================================================================= - property_ref = f"{progress_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.Animated") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - # ================================================================================= - override_ref = f"{property_ref}.IdleOverride" - override_raw_api_object = RawAPIObject(override_ref, - "IdleOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, property_ref) - override_raw_api_object.set_location(override_location) - - idle_forward_ref = ForwardRef(line, f"{game_entity_name}.Idle") - override_raw_api_object.add_raw_member("ability", - idle_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - garrison_animation_id, - override_ref, - "Idle", - "idle_garrison_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - line.add_raw_api_object(override_raw_api_object) - # ================================================================================= - override_forward_ref = ForwardRef(line, override_ref) - property_raw_api_object.add_raw_member("overrides", - [override_forward_ref], - "engine.util.progress.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - - properties.update({ - api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_ref = ForwardRef(line, progress_ref) - carry_progress.append(progress_forward_ref) - line.add_raw_api_object(progress_raw_api_object) - - container_raw_api_object.add_raw_member("carry_progress", - carry_progress, - "engine.util.storage.EntityContainer") - - line.add_raw_api_object(container_raw_api_object) - # ============================================================================== - container_forward_ref = ForwardRef(line, container_name) - ability_raw_api_object.add_raw_member("container", - container_forward_ref, - "engine.ability.type.Storage") - - # Empty condition - if garrison_mode in (GenieGarrisonMode.UNIT_GARRISON, GenieGarrisonMode.MONK): - # Empty before death - condition = [ - dataset.pregen_nyan_objects["util.logic.literal.death.StandardHealthDeathLiteral"].get_nyan_object()] - - elif garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): - # Empty when HP < 20% - condition = [ - dataset.pregen_nyan_objects["util.logic.literal.garrison.BuildingDamageEmpty"].get_nyan_object()] - - else: - # Never empty automatically (transport ships) - condition = [] - - ability_raw_api_object.add_raw_member("empty_condition", - condition, - "engine.ability.type.Storage") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def terrain_requirement_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the TerrainRequirement to a line. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward references for the abilities. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( - dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.TerrainRequirement" - ability_raw_api_object = RawAPIObject(ability_ref, - "TerrainRequirement", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.TerrainRequirement") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Allowed types - allowed_types = [] - terrain_restriction = current_unit["terrain_restriction"].value - for terrain_type in terrain_type_lookup_dict.values(): - # Check if terrain type is covered by terrain restriction - if terrain_restriction in terrain_type[1]: - type_name = f"util.terrain_type.types.{terrain_type[2]}" - type_obj = dataset.pregen_nyan_objects[type_name].get_nyan_object() - allowed_types.append(type_obj) - - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.TerrainRequirement") - - # Blacklisted terrains - ability_raw_api_object.add_raw_member("blacklisted_terrains", - [], - "engine.ability.type.TerrainRequirement") - - 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 trade_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Trade 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 = line.get_head_unit() - 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}.Trade" - ability_raw_api_object = RawAPIObject(ability_ref, "Trade", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Trade") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Trade route (use the trade route to the market) - trade_routes = [] - - unit_commands = current_unit["unit_commands"].value - for command in unit_commands: - # Find the trade command and the trade post id - type_id = command["type"].value - - if type_id != 111: - continue - - trade_post_id = command["unit_id"].value - if trade_post_id not in dataset.building_lines.keys(): - # Skips trade workshop - continue - - trade_post_line = dataset.building_lines[trade_post_id] - trade_post_name = name_lookup_dict[trade_post_id][0] - - trade_route_ref = f"{trade_post_name}.TradePost.AoE2{trade_post_name}TradeRoute" - trade_route_forward_ref = ForwardRef(trade_post_line, trade_route_ref) - trade_routes.append(trade_route_forward_ref) - - ability_raw_api_object.add_raw_member("trade_routes", - trade_routes, - "engine.ability.type.Trade") - - # container - container_forward_ref = ForwardRef( - line, f"{game_entity_name}.ResourceStorage.TradeContainer") - ability_raw_api_object.add_raw_member("container", - container_forward_ref, - "engine.ability.type.Trade") - - 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 trade_post_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the TradePost 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}.TradePost" - ability_raw_api_object = RawAPIObject(ability_ref, - "TradePost", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.TradePost") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Trade route - trade_routes = [] - # ===================================================================================== - trade_route_name = f"AoE2{game_entity_name}TradeRoute" - trade_route_ref = f"{game_entity_name}.TradePost.{trade_route_name}" - trade_route_raw_api_object = RawAPIObject(trade_route_ref, - trade_route_name, - dataset.nyan_api_objects) - trade_route_raw_api_object.add_raw_parent("engine.util.trade_route.type.AoE2TradeRoute") - trade_route_location = ForwardRef(line, ability_ref) - trade_route_raw_api_object.set_location(trade_route_location) - - # Trade resource - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() - trade_route_raw_api_object.add_raw_member("trade_resource", - resource, - "engine.util.trade_route.TradeRoute") - - # Start- and endpoints - market_forward_ref = ForwardRef(line, game_entity_name) - trade_route_raw_api_object.add_raw_member("start_trade_post", - market_forward_ref, - "engine.util.trade_route.TradeRoute") - trade_route_raw_api_object.add_raw_member("end_trade_post", - market_forward_ref, - "engine.util.trade_route.TradeRoute") - - trade_route_forward_ref = ForwardRef(line, trade_route_ref) - trade_routes.append(trade_route_forward_ref) - - line.add_raw_api_object(trade_route_raw_api_object) - # ===================================================================================== - ability_raw_api_object.add_raw_member("trade_routes", - trade_routes, - "engine.ability.type.TradePost") - - 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 transfer_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the TransferStorage 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, None - """ - 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}.TransferStorage" - ability_raw_api_object = RawAPIObject(ability_ref, - "TransferStorage", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.TransferStorage") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # storage element - storage_entity = None - garrisoned_forward_ref = None - for garrisoned in line.garrison_entities: - creatable_type = garrisoned.get_head_unit()["creatable_type"].value - - if creatable_type == 4: - storage_name = name_lookup_dict[garrisoned.get_id()][0] - storage_entity = garrisoned - garrisoned_forward_ref = ForwardRef(storage_entity, storage_name) - - break - - else: - garrisoned = line.garrison_entities[0] - storage_name = name_lookup_dict[garrisoned.get_id()][0] - storage_entity = garrisoned - garrisoned_forward_ref = ForwardRef(storage_entity, storage_name) - - ability_raw_api_object.add_raw_member("storage_element", - garrisoned_forward_ref, - "engine.ability.type.TransferStorage") - - # Source container - source_ref = f"{game_entity_name}.Storage.{game_entity_name}Container" - source_forward_ref = ForwardRef(line, source_ref) - ability_raw_api_object.add_raw_member("source_container", - source_forward_ref, - "engine.ability.type.TransferStorage") - - # Target container - target = None - unit_commands = line.get_switch_unit()["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - # Deposit - if type_id == 136: - target_id = command["unit_id"].value - target = dataset.building_lines[target_id] - - target_name = name_lookup_dict[target.get_id()][0] - target_ref = f"{target_name}.Storage.{target_name}Container" - target_forward_ref = ForwardRef(target, target_ref) - ability_raw_api_object.add_raw_member("target_container", - target_forward_ref, - "engine.ability.type.TransferStorage") - - 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 turn_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Turn 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - 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}.Turn" - ability_raw_api_object = RawAPIObject(ability_ref, - "Turn", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Turn") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Speed - turn_speed_unmodified = current_unit["turn_speed"].value - - # Default case: Instant turning - turn_speed = MemberSpecialValue.NYAN_INF - - # Ships/Trebuchets turn slower - if turn_speed_unmodified > 0: - turn_yaw = current_unit["max_yaw_per_sec_moving"].value - - if not turn_yaw == FLOAT32_MAX: - turn_speed = degrees(turn_yaw) - - ability_raw_api_object.add_raw_member("turn_speed", - turn_speed, - "engine.ability.type.Turn") - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - - # Ability properties - properties = { - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - } - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def use_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the UseContingent 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 = line.get_head_unit() - 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}.UseContingent" - - # Check if contingents are stored in the unit before creating the ability - - # Stores the pop space - resource_storage = current_unit["resource_storage"].value - contingents = [] - for storage in resource_storage: - type_id = storage["type"].value - - if type_id == 11: - resource = dataset.pregen_nyan_objects["util.resource.types.PopulationSpace"].get_nyan_object( - ) - resource_name = "PopSpace" - - else: - continue - - amount = storage["amount"].value - - contingent_amount_name = f"{game_entity_name}.UseContingent.{resource_name}" - contingent_amount = RawAPIObject(contingent_amount_name, resource_name, - dataset.nyan_api_objects) - contingent_amount.add_raw_parent("engine.util.resource.ResourceAmount") - ability_forward_ref = ForwardRef(line, ability_ref) - contingent_amount.set_location(ability_forward_ref) - - contingent_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - contingent_amount.add_raw_member("amount", - amount, - "engine.util.resource.ResourceAmount") - - line.add_raw_api_object(contingent_amount) - contingent_amount_forward_ref = ForwardRef(line, - contingent_amount_name) - contingents.append(contingent_amount_forward_ref) - - if not contingents: - # Break out of function if no contingents were found - return None - - ability_raw_api_object = RawAPIObject(ability_ref, - "UseContingent", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.UseContingent") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - ability_raw_api_object.add_raw_member("amount", - contingents, - "engine.ability.type.UseContingent") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def visibility_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Visibility 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 - api_objects = dataset.nyan_api_objects - - 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}.Visibility" - ability_raw_api_object = RawAPIObject(ability_ref, "Visibility", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Visibility") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Units are not visible in fog... - visible = False - - # ...Buidings and scenery is though - if isinstance(line, (GenieBuildingLineGroup, GenieAmbientGroup)): - visible = True - - ability_raw_api_object.add_raw_member("visible_in_fog", visible, - "engine.ability.type.Visibility") - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [ - dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"], - dataset.pregen_nyan_objects["util.diplomatic_stance.types.Friendly"].get_nyan_object(), - dataset.pregen_nyan_objects["util.diplomatic_stance.types.Neutral"].get_nyan_object(), - dataset.pregen_nyan_objects["util.diplomatic_stance.types.Enemy"].get_nyan_object(), - dataset.pregen_nyan_objects["util.diplomatic_stance.types.Gaia"].get_nyan_object() - ] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - - # Ability properties - properties = { - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - } - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Add another Visibility ability for buildings with construction progress = 0.0 - # It is not returned by this method, but referenced by the Constructable ability - if isinstance(line, GenieBuildingLineGroup): - ability_ref = f"{game_entity_name}.VisibilityConstruct0" - ability_raw_api_object = RawAPIObject(ability_ref, - "VisibilityConstruct0", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Visibility") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # The construction site is not visible in fog - visible = False - ability_raw_api_object.add_raw_member("visible_in_fog", visible, - "engine.ability.type.Visibility") - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Only the player and friendly players can see the construction site - diplomatic_stances = [ - dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"], - dataset.pregen_nyan_objects["util.diplomatic_stance.types.Friendly"].get_nyan_object( - ) - ] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - - # Ability properties - properties = { - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - } - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def create_animation( - line: GenieGameEntityGroup, - animation_id: int, - location_ref: str, - obj_name_prefix: str, - filename_prefix: str - ) -> ForwardRef: - """ - Generates an animation for an ability. - - :param line: ConverterObjectGroup that the animation object is added to. - :type line: ConverterObjectGroup - :param animation_id: ID of the animation in the dataset. - :type animation_id: int - :param ability_ref: Reference of the object the animation is nested in. - :type ability_ref: str - :param obj_name_prefix: Name prefix for the animation object. - :type obj_name_prefix: str - :param filename_prefix: Prefix for the animation PNG and sprite files. - :type filename_prefix: str - """ - dataset = line.data - head_unit_id = line.get_head_unit_id() - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - animation_ref = f"{location_ref}.{obj_name_prefix}Animation" - animation_obj_name = f"{obj_name_prefix}Animation" - animation_raw_api_object = RawAPIObject(animation_ref, animation_obj_name, - dataset.nyan_api_objects) - animation_raw_api_object.add_raw_parent("engine.util.graphics.Animation") - animation_location = ForwardRef(line, location_ref) - animation_raw_api_object.set_location(animation_location) - - if animation_id in dataset.combined_sprites.keys(): - ability_sprite = dataset.combined_sprites[animation_id] - - else: - ability_sprite = CombinedSprite(animation_id, - (f"{filename_prefix}" - f"{name_lookup_dict[head_unit_id][1]}"), - dataset) - dataset.combined_sprites.update({ability_sprite.get_id(): ability_sprite}) - - ability_sprite.add_reference(animation_raw_api_object) - - animation_raw_api_object.add_raw_member("sprite", ability_sprite, - "engine.util.graphics.Animation") - - line.add_raw_api_object(animation_raw_api_object) - - animation_forward_ref = ForwardRef(line, animation_ref) - - return animation_forward_ref - - @staticmethod - def create_civ_animation( - line: GenieGameEntityGroup, - civ_group: GenieCivilizationGroup, - animation_id: int, - location_ref: str, - obj_name_prefix: str, - filename_prefix: str, - exists: bool = False - ) -> None: - """ - Generates an animation as a patch for a civ. - - :param line: ConverterObjectGroup that the animation object is added to. - :type line: ConverterObjectGroup - :param civ_group: ConverterObjectGroup that patches the animation object into the ability. - :type civ_group: ConverterObjectGroup - :param animation_id: ID of the animation in the dataset. - :type animation_id: int - :param location_ref: Reference of the object the resulting object is nested in. - :type location_ref: str - :param obj_name_prefix: Name prefix for the object. - :type obj_name_prefix: str - :param filename_prefix: Prefix for the animation PNG and sprite files. - :type filename_prefix: str - :param exists: Tells the method if the animation object has already been created. - :type exists: bool - """ - dataset = civ_group.data - head_unit_id = line.get_head_unit_id() - civ_id = civ_group.get_id() - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - civ_name = civ_lookup_dict[civ_id][0] - - patch_target_ref = f"{location_ref}" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"{game_entity_name}{obj_name_prefix}AnimationWrapper" - wrapper_ref = f"{civ_name}.{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") - wrapper_raw_api_object.set_location(ForwardRef(civ_group, civ_name)) - - # Nyan patch - nyan_patch_name = f"{game_entity_name}{obj_name_prefix}Animation" - nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(civ_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 animation_id > -1: - # If the animation object already exists, we do not need to create it again - if exists: - # Point to a previously created animation object - animation_ref = f"{location_ref}.{obj_name_prefix}Animation" - animation_forward_ref = ForwardRef(line, animation_ref) - - else: - # Create the animation object - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - animation_id, - location_ref, - obj_name_prefix, - filename_prefix) - - # Patch animation into ability - nyan_patch_raw_api_object.add_raw_patch_member( - "animations", - [animation_forward_ref], - "engine.ability.property.type.Animated", - MemberOperator.ASSIGN - ) - - else: - # No animation -> empty the set - nyan_patch_raw_api_object.add_raw_patch_member( - "animations", - [], - "engine.ability.property.type.Animated", - MemberOperator.ASSIGN - ) - - patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - civ_group.add_raw_api_object(wrapper_raw_api_object) - civ_group.add_raw_api_object(nyan_patch_raw_api_object) - - # Add patch to game_setup - civ_forward_ref = ForwardRef(civ_group, civ_name) - wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) - push_object = RawMemberPush(civ_forward_ref, - "game_setup", - "engine.util.setup.PlayerSetup", - [wrapper_forward_ref]) - civ_group.add_raw_member_push(push_object) - - @staticmethod - def create_sound( - line: GenieGameEntityGroup, - sound_id: int, - location_ref: str, - obj_name_prefix: str, - filename_prefix: str - ) -> ForwardRef: - """ - Generates a sound for an ability. - - :param line: ConverterObjectGroup that the animation object is added to. - :type line: ConverterObjectGroup - :param sound_id: ID of the sound in the dataset. - :type sound_id: int - :param location_ref: Reference of the object the sound is nested in. - :type location_ref: str - :param obj_name_prefix: Name prefix for the sound object. - :type obj_name_prefix: str - :param filename_prefix: Prefix for the animation PNG and sprite files. - :type filename_prefix: str - """ - dataset = line.data - - sound_ref = f"{location_ref}.{obj_name_prefix}Sound" - sound_obj_name = f"{obj_name_prefix}Sound" - sound_raw_api_object = RawAPIObject(sound_ref, sound_obj_name, - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(line, location_ref) - sound_raw_api_object.set_location(sound_location) - - # Search for the sound if it exists - sounds_set = [] - - genie_sound = dataset.genie_sounds[sound_id] - file_ids = genie_sound.get_sounds(civ_id=-1) - - for file_id in file_ids: - if file_id in dataset.combined_sounds: - sound = dataset.combined_sounds[file_id] - - else: - sound = CombinedSound(sound_id, - file_id, - f"{filename_prefix}sound_{str(file_id)}", - dataset) - dataset.combined_sounds.update({file_id: sound}) - - sound.add_reference(sound_raw_api_object) - sounds_set.append(sound) - - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - sounds_set, - "engine.util.sound.Sound") - - line.add_raw_api_object(sound_raw_api_object) - - sound_forward_ref = ForwardRef(line, sound_ref) - - return sound_forward_ref - - @staticmethod - def create_language_strings( - line: GenieGameEntityGroup, - string_id: int, - location_ref: str, - obj_name_prefix: str - ) -> list[ForwardRef]: - """ - Generates a language string for an ability. - - :param line: ConverterObjectGroup that the animation object is added to. - :type line: ConverterObjectGroup - :param string_id: ID of the string in the dataset. - :type string_id: int - :param location_ref: Reference of the object the string is nested in. - :type location_ref: str - :param obj_name_prefix: Name prefix for the string object. - :type obj_name_prefix: str - """ - dataset = line.data - string_resources = dataset.strings.get_tables() - - string_objs = [] - for language, strings in string_resources.items(): - if string_id in strings.keys(): - string_name = f"{obj_name_prefix}String" - string_ref = f"{location_ref}.{string_name}" - string_raw_api_object = RawAPIObject(string_ref, string_name, - dataset.nyan_api_objects) - string_raw_api_object.add_raw_parent("engine.util.language.LanguageTextPair") - string_location = ForwardRef(line, location_ref) - string_raw_api_object.set_location(string_location) - - # Language identifier - lang_ref = f"util.language.{language}" - lang_forward_ref = dataset.pregen_nyan_objects[lang_ref].get_nyan_object() - string_raw_api_object.add_raw_member("language", - lang_forward_ref, - "engine.util.language.LanguageTextPair") - - # String - string_raw_api_object.add_raw_member("string", - strings[string_id], - "engine.util.language.LanguageTextPair") - - line.add_raw_api_object(string_raw_api_object) - string_forward_ref = ForwardRef(line, string_ref) - string_objs.append(string_forward_ref) - - return string_objs + active_transform_to_ability = staticmethod(active_transform_to_ability) + activity_ability = staticmethod(activity_ability) + apply_continuous_effect_ability = staticmethod(apply_continuous_effect_ability) + apply_discrete_effect_ability = staticmethod(apply_discrete_effect_ability) + attribute_change_tracker_ability = staticmethod(attribute_change_tracker_ability) + collect_storage_ability = staticmethod(collect_storage_ability) + collision_ability = staticmethod(collision_ability) + constructable_ability = staticmethod(constructable_ability) + create_ability = staticmethod(create_ability) + death_ability = staticmethod(death_ability) + delete_ability = staticmethod(delete_ability) + despawn_ability = staticmethod(despawn_ability) + drop_resources_ability = staticmethod(drop_resources_ability) + drop_site_ability = staticmethod(drop_site_ability) + enter_container_ability = staticmethod(enter_container_ability) + exchange_resources_ability = staticmethod(exchange_resources_ability) + exit_container_ability = staticmethod(exit_container_ability) + game_entity_stance_ability = staticmethod(game_entity_stance_ability) + formation_ability = staticmethod(formation_ability) + foundation_ability = staticmethod(foundation_ability) + gather_ability = staticmethod(gather_ability) + harvestable_ability = staticmethod(harvestable_ability) + herd_ability = staticmethod(herd_ability) + herdable_ability = staticmethod(herdable_ability) + idle_ability = staticmethod(idle_ability) + los_ability = staticmethod(line_of_sight_ability) + live_ability = staticmethod(live_ability) + move_ability = staticmethod(move_ability) + move_projectile_ability = staticmethod(move_projectile_ability) + named_ability = staticmethod(named_ability) + overlay_terrain_ability = staticmethod(overlay_terrain_ability) + pathable_ability = staticmethod(pathable_ability) + production_queue_ability = staticmethod(production_queue_ability) + projectile_ability = staticmethod(projectile_ability) + provide_contingent_ability = staticmethod(provide_contingent_ability) + rally_point_ability = staticmethod(rally_point_ability) + regenerate_attribute_ability = staticmethod(regenerate_attribute_ability) + regenerate_resource_spot_ability = staticmethod(regenerate_resource_spot_ability) + remove_storage_ability = staticmethod(remove_storage_ability) + research_ability = staticmethod(research_ability) + resource_storage_ability = staticmethod(resource_storage_ability) + resistance_ability = staticmethod(resistance_ability) + restock_ability = staticmethod(restock_ability) + selectable_ability = staticmethod(selectable_ability) + send_back_to_task_ability = staticmethod(send_back_to_task_ability) + shoot_projectile_ability = staticmethod(shoot_projectile_ability) + stop_ability = staticmethod(stop_ability) + storage_ability = staticmethod(storage_ability) + terrain_requirement_ability = staticmethod(terrain_requirement_ability) + trade_ability = staticmethod(trade_ability) + trade_post_ability = staticmethod(trade_post_ability) + transfer_storage_ability = staticmethod(transfer_storage_ability) + turn_ability = staticmethod(turn_ability) + use_contingent_ability = staticmethod(use_contingent_ability) + visibility_ability = staticmethod(visibility_ability) + + create_animation = staticmethod(create_animation) + create_civ_animation = staticmethod(create_civ_animation) + create_sound = staticmethod(create_sound) + create_language_strings = staticmethod(create_language_strings) diff --git a/openage/convert/processor/conversion/aoc/effect/CMakeLists.txt b/openage/convert/processor/conversion/aoc/effect/CMakeLists.txt new file mode 100644 index 0000000000..bd76227364 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/CMakeLists.txt @@ -0,0 +1,8 @@ +add_py_modules( + __init__.py + attack.py + construct.py + convert.py + heal.py + repair.py +) diff --git a/openage/convert/processor/conversion/aoc/effect/__init__.py b/openage/convert/processor/conversion/aoc/effect/__init__.py new file mode 100644 index 0000000000..7fac6ac98d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates effects for the Apply*Effect abilities. +""" diff --git a/openage/convert/processor/conversion/aoc/effect/attack.py b/openage/convert/processor/conversion/aoc/effect/attack.py new file mode 100644 index 0000000000..a918429a93 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/attack.py @@ -0,0 +1,118 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create effects for attacking units and buildings. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_attack_effects( + line: GenieGameEntityGroup, + location_ref: str, + projectile: int = -1 +) -> list[ForwardRef]: + """ + Creates effects that are used for attacking (unit command: 7) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param location_ref: Reference to API object the effects are added to. + :type location_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + dataset = line.data + + if projectile != 1: + current_unit = line.get_head_unit() + + else: + projectile_id = line.get_head_unit()["projectile_id1"].value + current_unit = dataset.genie_units[projectile_id] + + effects = [] + + armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) + + # FlatAttributeChangeDecrease + effect_parent = "engine.effect.discrete.flat_attribute_change.FlatAttributeChange" + attack_parent = "engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" + + attacks = current_unit["attacks"].value + + for attack in attacks.values(): + armor_class = attack["type_id"].value + attack_amount = attack["amount"].value + class_name = armor_lookup_dict[armor_class] + + attack_ref = f"{location_ref}.{class_name}" + attack_raw_api_object = RawAPIObject(attack_ref, + class_name, + dataset.nyan_api_objects) + attack_raw_api_object.add_raw_parent(attack_parent) + attack_location = ForwardRef(line, location_ref) + attack_raw_api_object.set_location(attack_location) + + # Type + type_ref = f"util.attribute_change_type.types.{class_name}" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + attack_raw_api_object.add_raw_member("type", + change_type, + effect_parent) + + # Min value (optional) + min_value = dataset.pregen_nyan_objects[("effect.discrete.flat_attribute_change." + "min_damage.AoE2MinChangeAmount")].get_nyan_object() + attack_raw_api_object.add_raw_member("min_change_value", + min_value, + effect_parent) + + # Max value (optional; not added because there is none in AoE2) + + # Change value + # ================================================================================= + amount_name = f"{location_ref}.{class_name}.ChangeAmount" + amount_raw_api_object = RawAPIObject( + amount_name, "ChangeAmount", dataset.nyan_api_objects) + amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") + amount_location = ForwardRef(line, attack_ref) + amount_raw_api_object.set_location(amount_location) + + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + amount_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeAmount") + amount_raw_api_object.add_raw_member("amount", + attack_amount, + "engine.util.attribute.AttributeAmount") + + line.add_raw_api_object(amount_raw_api_object) + # ================================================================================= + amount_forward_ref = ForwardRef(line, amount_name) + attack_raw_api_object.add_raw_member("change_value", + amount_forward_ref, + effect_parent) + + # Ignore protection + attack_raw_api_object.add_raw_member("ignore_protection", + [], + effect_parent) + + line.add_raw_api_object(attack_raw_api_object) + attack_forward_ref = ForwardRef(line, attack_ref) + effects.append(attack_forward_ref) + + # Fallback effect + fallback_effect = dataset.pregen_nyan_objects[("effect.discrete.flat_attribute_change." + "fallback.AoE2AttackFallback")].get_nyan_object() + effects.append(fallback_effect) + + return effects diff --git a/openage/convert/processor/conversion/aoc/effect/construct.py b/openage/convert/processor/conversion/aoc/effect/construct.py new file mode 100644 index 0000000000..1a41c9d096 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/construct.py @@ -0,0 +1,107 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create effects for constructing buildings. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_construct_effects( + line: GenieGameEntityGroup, + location_ref: str +) -> list[ForwardRef]: + """ + Creates effects that are used for construction (unit command: 101) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param location_ref: Reference to API object the effects are added to. + :type location_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + effects = [] + + progress_effect_parent = "engine.effect.continuous.time_relative_progress.TimeRelativeProgressChange" + progress_construct_parent = "engine.effect.continuous.time_relative_progress.type.TimeRelativeProgressIncrease" + attr_effect_parent = "engine.effect.continuous.time_relative_attribute.TimeRelativeAttributeChange" + attr_construct_parent = "engine.effect.continuous.time_relative_attribute.type.TimeRelativeAttributeIncrease" + + constructable_lines = [] + constructable_lines.extend(dataset.building_lines.values()) + + for constructable_line in constructable_lines: + game_entity_name = name_lookup_dict[constructable_line.get_head_unit_id()][0] + + # Construction progress + contruct_progress_name = f"{game_entity_name}ConstructProgressEffect" + contruct_progress_ref = f"{location_ref}.{contruct_progress_name}" + contruct_progress_raw_api_object = RawAPIObject(contruct_progress_ref, + contruct_progress_name, + dataset.nyan_api_objects) + contruct_progress_raw_api_object.add_raw_parent(progress_construct_parent) + contruct_progress_location = ForwardRef(line, location_ref) + contruct_progress_raw_api_object.set_location(contruct_progress_location) + + # Type + type_ref = f"util.construct_type.types.{game_entity_name}Construct" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + contruct_progress_raw_api_object.add_raw_member("type", + change_type, + progress_effect_parent) + + # Total change time + change_time = constructable_line.get_head_unit()["creation_time"].value + contruct_progress_raw_api_object.add_raw_member("total_change_time", + change_time, + progress_effect_parent) + + line.add_raw_api_object(contruct_progress_raw_api_object) + contruct_progress_forward_ref = ForwardRef(line, contruct_progress_ref) + effects.append(contruct_progress_forward_ref) + + # HP increase during construction + contruct_hp_name = f"{game_entity_name}ConstructHPEffect" + contruct_hp_ref = f"{location_ref}.{contruct_hp_name}" + contruct_hp_raw_api_object = RawAPIObject(contruct_hp_ref, + contruct_hp_name, + dataset.nyan_api_objects) + contruct_hp_raw_api_object.add_raw_parent(attr_construct_parent) + contruct_hp_location = ForwardRef(line, location_ref) + contruct_hp_raw_api_object.set_location(contruct_hp_location) + + # Type + type_ref = f"util.attribute_change_type.types.{game_entity_name}Construct" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + contruct_hp_raw_api_object.add_raw_member("type", + change_type, + attr_effect_parent) + + # Total change time + change_time = constructable_line.get_head_unit()["creation_time"].value + contruct_hp_raw_api_object.add_raw_member("total_change_time", + change_time, + attr_effect_parent) + + # Ignore protection + contruct_hp_raw_api_object.add_raw_member("ignore_protection", + [], + attr_effect_parent) + + line.add_raw_api_object(contruct_hp_raw_api_object) + contruct_hp_forward_ref = ForwardRef(line, contruct_hp_ref) + effects.append(contruct_hp_forward_ref) + + return effects diff --git a/openage/convert/processor/conversion/aoc/effect/convert.py b/openage/convert/processor/conversion/aoc/effect/convert.py new file mode 100644 index 0000000000..dcea8dae05 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/convert.py @@ -0,0 +1,136 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create effects for converting units and buildings. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_convert_effects( + line: GenieGameEntityGroup, + location_ref: str +) -> list[ForwardRef]: + """ + Creates effects that are used for conversion (unit command: 104) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param location_ref: Reference to API object the effects are added to. + :type location_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + current_unit = line.get_head_unit() + dataset = line.data + + effects = [] + + effect_parent = "engine.effect.discrete.convert.Convert" + convert_parent = "engine.effect.discrete.convert.type.AoE2Convert" + + unit_commands = current_unit["unit_commands"].value + for command in unit_commands: + # Find the Heal command. + type_id = command["type"].value + + if type_id == 104: + skip_guaranteed_rounds = -1 * command["work_value1"].value + skip_protected_rounds = -1 * command["work_value2"].value + break + + else: + # Return the empty set + return effects + + # Unit conversion + convert_ref = f"{location_ref}.ConvertUnitEffect" + convert_raw_api_object = RawAPIObject(convert_ref, + "ConvertUnitEffect", + dataset.nyan_api_objects) + convert_raw_api_object.add_raw_parent(convert_parent) + convert_location = ForwardRef(line, location_ref) + convert_raw_api_object.set_location(convert_location) + + # Type + type_ref = "util.convert_type.types.UnitConvert" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + convert_raw_api_object.add_raw_member("type", + change_type, + effect_parent) + + # Min success (optional; not added because there is none in AoE2) + # Max success (optional; not added because there is none in AoE2) + + # Chance + # hardcoded resource + chance_success = dataset.genie_civs[0]["resources"][182].value / 100 + convert_raw_api_object.add_raw_member("chance_success", + chance_success, + effect_parent) + + # Fail cost (optional; not added because there is none in AoE2) + + # Guaranteed rounds skip + convert_raw_api_object.add_raw_member("skip_guaranteed_rounds", + skip_guaranteed_rounds, + convert_parent) + + # Protected rounds skip + convert_raw_api_object.add_raw_member("skip_protected_rounds", + skip_protected_rounds, + convert_parent) + + line.add_raw_api_object(convert_raw_api_object) + attack_forward_ref = ForwardRef(line, convert_ref) + effects.append(attack_forward_ref) + + # Building conversion + convert_ref = f"{location_ref}.ConvertBuildingEffect" + convert_raw_api_object = RawAPIObject(convert_ref, + "ConvertBuildingUnitEffect", + dataset.nyan_api_objects) + convert_raw_api_object.add_raw_parent(convert_parent) + convert_location = ForwardRef(line, location_ref) + convert_raw_api_object.set_location(convert_location) + + # Type + type_ref = "util.convert_type.types.BuildingConvert" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + convert_raw_api_object.add_raw_member("type", + change_type, + effect_parent) + + # Min success (optional; not added because there is none in AoE2) + # Max success (optional; not added because there is none in AoE2) + + # Chance + # hardcoded resource + chance_success = dataset.genie_civs[0]["resources"][182].value / 100 + convert_raw_api_object.add_raw_member("chance_success", + chance_success, + effect_parent) + + # Fail cost (optional; not added because there is none in AoE2) + + # Guaranteed rounds skip + convert_raw_api_object.add_raw_member("skip_guaranteed_rounds", + 0, + convert_parent) + + # Protected rounds skip + convert_raw_api_object.add_raw_member("skip_protected_rounds", + 0, + convert_parent) + + line.add_raw_api_object(convert_raw_api_object) + attack_forward_ref = ForwardRef(line, convert_ref) + effects.append(attack_forward_ref) + + return effects diff --git a/openage/convert/processor/conversion/aoc/effect/heal.py b/openage/convert/processor/conversion/aoc/effect/heal.py new file mode 100644 index 0000000000..d12149f784 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/heal.py @@ -0,0 +1,111 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create effects for healing units. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_heal_effects( + line: GenieGameEntityGroup, + location_ref: str +) -> list[ForwardRef]: + """ + Creates effects that are used for healing (unit command: 105) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param location_ref: Reference to API object the effects are added to. + :type location_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + current_unit = line.get_head_unit() + dataset = line.data + + effects = [] + + effect_parent = "engine.effect.continuous.flat_attribute_change.FlatAttributeChange" + heal_parent = "engine.effect.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease" + + unit_commands = current_unit["unit_commands"].value + heal_command = None + + for command in unit_commands: + # Find the Heal command. + type_id = command["type"].value + + if type_id == 105: + heal_command = command + break + + else: + # Return the empty set + return effects + + heal_rate = heal_command["work_value1"].value + + heal_ref = f"{location_ref}.HealEffect" + heal_raw_api_object = RawAPIObject(heal_ref, + "HealEffect", + dataset.nyan_api_objects) + heal_raw_api_object.add_raw_parent(heal_parent) + heal_location = ForwardRef(line, location_ref) + heal_raw_api_object.set_location(heal_location) + + # Type + type_ref = "util.attribute_change_type.types.Heal" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + heal_raw_api_object.add_raw_member("type", + change_type, + effect_parent) + + # Min value (optional) + min_value = dataset.pregen_nyan_objects[("effect.discrete.flat_attribute_change." + "min_heal.AoE2MinChangeAmount")].get_nyan_object() + heal_raw_api_object.add_raw_member("min_change_rate", + min_value, + effect_parent) + + # Max value (optional; not added because there is none in AoE2) + + # Change rate + # ================================================================================= + rate_name = f"{location_ref}.HealEffect.ChangeRate" + rate_raw_api_object = RawAPIObject(rate_name, "ChangeRate", dataset.nyan_api_objects) + rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") + rate_location = ForwardRef(line, heal_ref) + rate_raw_api_object.set_location(rate_location) + + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + rate_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeRate") + rate_raw_api_object.add_raw_member("rate", + heal_rate, + "engine.util.attribute.AttributeRate") + + line.add_raw_api_object(rate_raw_api_object) + # ================================================================================= + rate_forward_ref = ForwardRef(line, rate_name) + heal_raw_api_object.add_raw_member("change_rate", + rate_forward_ref, + effect_parent) + + # Ignore protection + heal_raw_api_object.add_raw_member("ignore_protection", + [], + effect_parent) + + line.add_raw_api_object(heal_raw_api_object) + heal_forward_ref = ForwardRef(line, heal_ref) + effects.append(heal_forward_ref) + + return effects diff --git a/openage/convert/processor/conversion/aoc/effect/repair.py b/openage/convert/processor/conversion/aoc/effect/repair.py new file mode 100644 index 0000000000..a759f6fbe7 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/repair.py @@ -0,0 +1,140 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create effects for repairing buildings. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_repair_effects( + line: GenieGameEntityGroup, + location_ref: str +) -> list[ForwardRef]: + """ + Creates effects that are used for repairing (unit command: 106) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param location_ref: Reference to API object the effects are added to. + :type location_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + effects = [] + + effect_parent = "engine.effect.continuous.flat_attribute_change.FlatAttributeChange" + repair_parent = "engine.effect.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease" + + repairable_lines = [] + repairable_lines.extend(dataset.building_lines.values()) + for unit_line in dataset.unit_lines.values(): + if unit_line.is_repairable(): + repairable_lines.append(unit_line) + + for repairable_line in repairable_lines: + game_entity_name = name_lookup_dict[repairable_line.get_head_unit_id()][0] + + repair_name = f"{game_entity_name}RepairEffect" + repair_ref = f"{location_ref}.{repair_name}" + repair_raw_api_object = RawAPIObject(repair_ref, + repair_name, + dataset.nyan_api_objects) + repair_raw_api_object.add_raw_parent(repair_parent) + repair_location = ForwardRef(line, location_ref) + repair_raw_api_object.set_location(repair_location) + + line.add_raw_api_object(repair_raw_api_object) + + # Type + type_ref = f"util.attribute_change_type.types.{game_entity_name}Repair" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + repair_raw_api_object.add_raw_member("type", + change_type, + effect_parent) + + # Min value (optional; not added because buildings don't block repairing) + + # Max value (optional; not added because there is none in AoE2) + + # Change rate + # ================================================================================= + rate_name = f"{location_ref}.{repair_name}.ChangeRate" + rate_raw_api_object = RawAPIObject(rate_name, "ChangeRate", dataset.nyan_api_objects) + rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") + rate_location = ForwardRef(line, repair_ref) + rate_raw_api_object.set_location(rate_location) + + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + rate_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeRate") + + # Hardcoded repair rate: + # - Buildings: 750 HP/min = 12.5 HP/s + # - Ships/Siege: 187.5 HP/min = 3.125 HP/s + if isinstance(repairable_line, GenieBuildingLineGroup): + repair_rate = 12.5 + + else: + repair_rate = 3.125 + + rate_raw_api_object.add_raw_member("rate", + repair_rate, + "engine.util.attribute.AttributeRate") + + line.add_raw_api_object(rate_raw_api_object) + # ================================================================================= + rate_forward_ref = ForwardRef(line, rate_name) + repair_raw_api_object.add_raw_member("change_rate", + rate_forward_ref, + effect_parent) + + # Ignore protection + repair_raw_api_object.add_raw_member("ignore_protection", + [], + effect_parent) + + # Repair cost + property_ref = f"{repair_ref}.Cost" + property_raw_api_object = RawAPIObject(property_ref, + "Cost", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.effect.property.type.Cost") + property_location = ForwardRef(line, repair_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + cost_ref = f"{game_entity_name}.CreatableGameEntity.{game_entity_name}RepairCost" + cost_forward_ref = ForwardRef(repairable_line, cost_ref) + property_raw_api_object.add_raw_member("cost", + cost_forward_ref, + "engine.effect.property.type.Cost") + + property_forward_ref = ForwardRef(line, property_ref) + properties = { + api_objects["engine.effect.property.type.Cost"]: property_forward_ref + } + + repair_raw_api_object.add_raw_member("properties", + properties, + "engine.effect.Effect") + + repair_forward_ref = ForwardRef(line, repair_ref) + effects.append(repair_forward_ref) + + return effects diff --git a/openage/convert/processor/conversion/aoc/effect_subprocessor.py b/openage/convert/processor/conversion/aoc/effect_subprocessor.py index 1e9256a4d9..e1f7cfbe63 100644 --- a/openage/convert/processor/conversion/aoc/effect_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/effect_subprocessor.py @@ -1,25 +1,21 @@ -# 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-statements,invalid-name -# -# TODO: -# pylint: disable=line-too-long +# pylint: disable=too-few-public-methods """ Creates effects and resistances for the Apply*Effect and Resistance abilities. """ -from __future__ import annotations -import typing - -from ....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup, \ - GenieBuildingLineGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup +from .effect.attack import get_attack_effects +from .effect.construct import get_construct_effects +from .effect.convert import get_convert_effects +from .effect.heal import get_heal_effects +from .effect.repair import get_repair_effects +from .resistance.attack import get_attack_resistances +from .resistance.convert import get_convert_resistances +from .resistance.heal import get_heal_resistances +from .resistance.repair import get_repair_resistances +from .resistance.construct import get_construct_resistances class AoCEffectSubprocessor: @@ -27,955 +23,14 @@ class AoCEffectSubprocessor: Creates raw API objects for attacks/resistances in AoC. """ - @staticmethod - def get_attack_effects( - line: GenieGameEntityGroup, - location_ref: str, - projectile: int = -1 - ) -> list[ForwardRef]: - """ - Creates effects that are used for attacking (unit command: 7) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param location_ref: Reference to API object the effects are added to. - :type location_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - dataset = line.data - - if projectile != 1: - current_unit = line.get_head_unit() - - else: - projectile_id = line.get_head_unit()["projectile_id1"].value - current_unit = dataset.genie_units[projectile_id] - - effects = [] - - armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) - - # FlatAttributeChangeDecrease - effect_parent = "engine.effect.discrete.flat_attribute_change.FlatAttributeChange" - attack_parent = "engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" - - attacks = current_unit["attacks"].value - - for attack in attacks.values(): - armor_class = attack["type_id"].value - attack_amount = attack["amount"].value - class_name = armor_lookup_dict[armor_class] - - attack_ref = f"{location_ref}.{class_name}" - attack_raw_api_object = RawAPIObject(attack_ref, - class_name, - dataset.nyan_api_objects) - attack_raw_api_object.add_raw_parent(attack_parent) - attack_location = ForwardRef(line, location_ref) - attack_raw_api_object.set_location(attack_location) - - # Type - type_ref = f"util.attribute_change_type.types.{class_name}" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - attack_raw_api_object.add_raw_member("type", - change_type, - effect_parent) - - # Min value (optional) - min_value = dataset.pregen_nyan_objects[("effect.discrete.flat_attribute_change." - "min_damage.AoE2MinChangeAmount")].get_nyan_object() - attack_raw_api_object.add_raw_member("min_change_value", - min_value, - effect_parent) - - # Max value (optional; not added because there is none in AoE2) - - # Change value - # ================================================================================= - amount_name = f"{location_ref}.{class_name}.ChangeAmount" - amount_raw_api_object = RawAPIObject( - amount_name, "ChangeAmount", dataset.nyan_api_objects) - amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") - amount_location = ForwardRef(line, attack_ref) - amount_raw_api_object.set_location(amount_location) - - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - amount_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeAmount") - amount_raw_api_object.add_raw_member("amount", - attack_amount, - "engine.util.attribute.AttributeAmount") - - line.add_raw_api_object(amount_raw_api_object) - # ================================================================================= - amount_forward_ref = ForwardRef(line, amount_name) - attack_raw_api_object.add_raw_member("change_value", - amount_forward_ref, - effect_parent) - - # Ignore protection - attack_raw_api_object.add_raw_member("ignore_protection", - [], - effect_parent) - - line.add_raw_api_object(attack_raw_api_object) - attack_forward_ref = ForwardRef(line, attack_ref) - effects.append(attack_forward_ref) - - # Fallback effect - fallback_effect = dataset.pregen_nyan_objects[("effect.discrete.flat_attribute_change." - "fallback.AoE2AttackFallback")].get_nyan_object() - effects.append(fallback_effect) - - return effects - - @staticmethod - def get_convert_effects( - line: GenieGameEntityGroup, - location_ref: str - ) -> list[ForwardRef]: - """ - Creates effects that are used for conversion (unit command: 104) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param location_ref: Reference to API object the effects are added to. - :type location_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - current_unit = line.get_head_unit() - dataset = line.data - - effects = [] - - effect_parent = "engine.effect.discrete.convert.Convert" - convert_parent = "engine.effect.discrete.convert.type.AoE2Convert" - - unit_commands = current_unit["unit_commands"].value - for command in unit_commands: - # Find the Heal command. - type_id = command["type"].value - - if type_id == 104: - skip_guaranteed_rounds = -1 * command["work_value1"].value - skip_protected_rounds = -1 * command["work_value2"].value - break - - else: - # Return the empty set - return effects - - # Unit conversion - convert_ref = f"{location_ref}.ConvertUnitEffect" - convert_raw_api_object = RawAPIObject(convert_ref, - "ConvertUnitEffect", - dataset.nyan_api_objects) - convert_raw_api_object.add_raw_parent(convert_parent) - convert_location = ForwardRef(line, location_ref) - convert_raw_api_object.set_location(convert_location) - - # Type - type_ref = "util.convert_type.types.UnitConvert" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - convert_raw_api_object.add_raw_member("type", - change_type, - effect_parent) - - # Min success (optional; not added because there is none in AoE2) - # Max success (optional; not added because there is none in AoE2) - - # Chance - # hardcoded resource - chance_success = dataset.genie_civs[0]["resources"][182].value / 100 - convert_raw_api_object.add_raw_member("chance_success", - chance_success, - effect_parent) - - # Fail cost (optional; not added because there is none in AoE2) - - # Guaranteed rounds skip - convert_raw_api_object.add_raw_member("skip_guaranteed_rounds", - skip_guaranteed_rounds, - convert_parent) - - # Protected rounds skip - convert_raw_api_object.add_raw_member("skip_protected_rounds", - skip_protected_rounds, - convert_parent) - - line.add_raw_api_object(convert_raw_api_object) - attack_forward_ref = ForwardRef(line, convert_ref) - effects.append(attack_forward_ref) - - # Building conversion - convert_ref = f"{location_ref}.ConvertBuildingEffect" - convert_raw_api_object = RawAPIObject(convert_ref, - "ConvertBuildingUnitEffect", - dataset.nyan_api_objects) - convert_raw_api_object.add_raw_parent(convert_parent) - convert_location = ForwardRef(line, location_ref) - convert_raw_api_object.set_location(convert_location) - - # Type - type_ref = "util.convert_type.types.BuildingConvert" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - convert_raw_api_object.add_raw_member("type", - change_type, - effect_parent) - - # Min success (optional; not added because there is none in AoE2) - # Max success (optional; not added because there is none in AoE2) - - # Chance - # hardcoded resource - chance_success = dataset.genie_civs[0]["resources"][182].value / 100 - convert_raw_api_object.add_raw_member("chance_success", - chance_success, - effect_parent) - - # Fail cost (optional; not added because there is none in AoE2) - - # Guaranteed rounds skip - convert_raw_api_object.add_raw_member("skip_guaranteed_rounds", - 0, - convert_parent) - - # Protected rounds skip - convert_raw_api_object.add_raw_member("skip_protected_rounds", - 0, - convert_parent) - - line.add_raw_api_object(convert_raw_api_object) - attack_forward_ref = ForwardRef(line, convert_ref) - effects.append(attack_forward_ref) - - return effects - - @staticmethod - def get_heal_effects( - line: GenieGameEntityGroup, - location_ref: str - ) -> list[ForwardRef]: - """ - Creates effects that are used for healing (unit command: 105) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param location_ref: Reference to API object the effects are added to. - :type location_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - current_unit = line.get_head_unit() - dataset = line.data - - effects = [] - - effect_parent = "engine.effect.continuous.flat_attribute_change.FlatAttributeChange" - heal_parent = "engine.effect.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease" - - unit_commands = current_unit["unit_commands"].value - heal_command = None - - for command in unit_commands: - # Find the Heal command. - type_id = command["type"].value - - if type_id == 105: - heal_command = command - break - - else: - # Return the empty set - return effects - - heal_rate = heal_command["work_value1"].value - - heal_ref = f"{location_ref}.HealEffect" - heal_raw_api_object = RawAPIObject(heal_ref, - "HealEffect", - dataset.nyan_api_objects) - heal_raw_api_object.add_raw_parent(heal_parent) - heal_location = ForwardRef(line, location_ref) - heal_raw_api_object.set_location(heal_location) - - # Type - type_ref = "util.attribute_change_type.types.Heal" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - heal_raw_api_object.add_raw_member("type", - change_type, - effect_parent) - - # Min value (optional) - min_value = dataset.pregen_nyan_objects[("effect.discrete.flat_attribute_change." - "min_heal.AoE2MinChangeAmount")].get_nyan_object() - heal_raw_api_object.add_raw_member("min_change_rate", - min_value, - effect_parent) - - # Max value (optional; not added because there is none in AoE2) - - # Change rate - # ================================================================================= - rate_name = f"{location_ref}.HealEffect.ChangeRate" - rate_raw_api_object = RawAPIObject(rate_name, "ChangeRate", dataset.nyan_api_objects) - rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") - rate_location = ForwardRef(line, heal_ref) - rate_raw_api_object.set_location(rate_location) - - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - rate_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeRate") - rate_raw_api_object.add_raw_member("rate", - heal_rate, - "engine.util.attribute.AttributeRate") - - line.add_raw_api_object(rate_raw_api_object) - # ================================================================================= - rate_forward_ref = ForwardRef(line, rate_name) - heal_raw_api_object.add_raw_member("change_rate", - rate_forward_ref, - effect_parent) - - # Ignore protection - heal_raw_api_object.add_raw_member("ignore_protection", - [], - effect_parent) - - line.add_raw_api_object(heal_raw_api_object) - heal_forward_ref = ForwardRef(line, heal_ref) - effects.append(heal_forward_ref) - - return effects - - @staticmethod - def get_repair_effects( - line: GenieGameEntityGroup, - location_ref: str - ) -> list[ForwardRef]: - """ - Creates effects that are used for repairing (unit command: 106) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param location_ref: Reference to API object the effects are added to. - :type location_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - effects = [] - - effect_parent = "engine.effect.continuous.flat_attribute_change.FlatAttributeChange" - repair_parent = "engine.effect.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease" - - repairable_lines = [] - repairable_lines.extend(dataset.building_lines.values()) - for unit_line in dataset.unit_lines.values(): - if unit_line.is_repairable(): - repairable_lines.append(unit_line) - - for repairable_line in repairable_lines: - game_entity_name = name_lookup_dict[repairable_line.get_head_unit_id()][0] - - repair_name = f"{game_entity_name}RepairEffect" - repair_ref = f"{location_ref}.{repair_name}" - repair_raw_api_object = RawAPIObject(repair_ref, - repair_name, - dataset.nyan_api_objects) - repair_raw_api_object.add_raw_parent(repair_parent) - repair_location = ForwardRef(line, location_ref) - repair_raw_api_object.set_location(repair_location) - - line.add_raw_api_object(repair_raw_api_object) - - # Type - type_ref = f"util.attribute_change_type.types.{game_entity_name}Repair" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - repair_raw_api_object.add_raw_member("type", - change_type, - effect_parent) - - # Min value (optional; not added because buildings don't block repairing) - - # Max value (optional; not added because there is none in AoE2) - - # Change rate - # ================================================================================= - rate_name = f"{location_ref}.{repair_name}.ChangeRate" - rate_raw_api_object = RawAPIObject(rate_name, "ChangeRate", dataset.nyan_api_objects) - rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") - rate_location = ForwardRef(line, repair_ref) - rate_raw_api_object.set_location(rate_location) - - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - rate_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeRate") - - # Hardcoded repair rate: - # - Buildings: 750 HP/min = 12.5 HP/s - # - Ships/Siege: 187.5 HP/min = 3.125 HP/s - if isinstance(repairable_line, GenieBuildingLineGroup): - repair_rate = 12.5 - - else: - repair_rate = 3.125 - - rate_raw_api_object.add_raw_member("rate", - repair_rate, - "engine.util.attribute.AttributeRate") - - line.add_raw_api_object(rate_raw_api_object) - # ================================================================================= - rate_forward_ref = ForwardRef(line, rate_name) - repair_raw_api_object.add_raw_member("change_rate", - rate_forward_ref, - effect_parent) - - # Ignore protection - repair_raw_api_object.add_raw_member("ignore_protection", - [], - effect_parent) - - # Repair cost - property_ref = f"{repair_ref}.Cost" - property_raw_api_object = RawAPIObject(property_ref, - "Cost", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.effect.property.type.Cost") - property_location = ForwardRef(line, repair_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - cost_ref = f"{game_entity_name}.CreatableGameEntity.{game_entity_name}RepairCost" - cost_forward_ref = ForwardRef(repairable_line, cost_ref) - property_raw_api_object.add_raw_member("cost", - cost_forward_ref, - "engine.effect.property.type.Cost") - - property_forward_ref = ForwardRef(line, property_ref) - properties = { - api_objects["engine.effect.property.type.Cost"]: property_forward_ref - } - - repair_raw_api_object.add_raw_member("properties", - properties, - "engine.effect.Effect") - - repair_forward_ref = ForwardRef(line, repair_ref) - effects.append(repair_forward_ref) - - return effects - - @staticmethod - def get_construct_effects( - line: GenieGameEntityGroup, - location_ref: str - ) -> list[ForwardRef]: - """ - Creates effects that are used for construction (unit command: 101) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param location_ref: Reference to API object the effects are added to. - :type location_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - dataset = line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - effects = [] - - progress_effect_parent = "engine.effect.continuous.time_relative_progress.TimeRelativeProgressChange" - progress_construct_parent = "engine.effect.continuous.time_relative_progress.type.TimeRelativeProgressIncrease" - attr_effect_parent = "engine.effect.continuous.time_relative_attribute.TimeRelativeAttributeChange" - attr_construct_parent = "engine.effect.continuous.time_relative_attribute.type.TimeRelativeAttributeIncrease" - - constructable_lines = [] - constructable_lines.extend(dataset.building_lines.values()) - - for constructable_line in constructable_lines: - game_entity_name = name_lookup_dict[constructable_line.get_head_unit_id()][0] - - # Construction progress - contruct_progress_name = f"{game_entity_name}ConstructProgressEffect" - contruct_progress_ref = f"{location_ref}.{contruct_progress_name}" - contruct_progress_raw_api_object = RawAPIObject(contruct_progress_ref, - contruct_progress_name, - dataset.nyan_api_objects) - contruct_progress_raw_api_object.add_raw_parent(progress_construct_parent) - contruct_progress_location = ForwardRef(line, location_ref) - contruct_progress_raw_api_object.set_location(contruct_progress_location) - - # Type - type_ref = f"util.construct_type.types.{game_entity_name}Construct" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - contruct_progress_raw_api_object.add_raw_member("type", - change_type, - progress_effect_parent) - - # Total change time - change_time = constructable_line.get_head_unit()["creation_time"].value - contruct_progress_raw_api_object.add_raw_member("total_change_time", - change_time, - progress_effect_parent) - - line.add_raw_api_object(contruct_progress_raw_api_object) - contruct_progress_forward_ref = ForwardRef(line, contruct_progress_ref) - effects.append(contruct_progress_forward_ref) - - # HP increase during construction - contruct_hp_name = f"{game_entity_name}ConstructHPEffect" - contruct_hp_ref = f"{location_ref}.{contruct_hp_name}" - contruct_hp_raw_api_object = RawAPIObject(contruct_hp_ref, - contruct_hp_name, - dataset.nyan_api_objects) - contruct_hp_raw_api_object.add_raw_parent(attr_construct_parent) - contruct_hp_location = ForwardRef(line, location_ref) - contruct_hp_raw_api_object.set_location(contruct_hp_location) - - # Type - type_ref = f"util.attribute_change_type.types.{game_entity_name}Construct" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - contruct_hp_raw_api_object.add_raw_member("type", - change_type, - attr_effect_parent) - - # Total change time - change_time = constructable_line.get_head_unit()["creation_time"].value - contruct_hp_raw_api_object.add_raw_member("total_change_time", - change_time, - attr_effect_parent) - - # Ignore protection - contruct_hp_raw_api_object.add_raw_member("ignore_protection", - [], - attr_effect_parent) - - line.add_raw_api_object(contruct_hp_raw_api_object) - contruct_hp_forward_ref = ForwardRef(line, contruct_hp_ref) - effects.append(contruct_hp_forward_ref) - - return effects - - @staticmethod - def get_attack_resistances( - line: GenieGameEntityGroup, - ability_ref: str - ) -> list[ForwardRef]: - """ - Creates resistances that are used for attacking (unit command: 7) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - current_unit = line.get_head_unit() - dataset = line.data - - armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) - - resistances = [] - - # FlatAttributeChangeDecrease - resistance_parent = "engine.resistance.discrete.flat_attribute_change.FlatAttributeChange" - armor_parent = "engine.resistance.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" - - if current_unit.has_member("armors"): - armors = current_unit["armors"].value - - else: - # TODO: Trees and blast defense - armors = {} - - for armor in armors.values(): - armor_class = armor["type_id"].value - armor_amount = armor["amount"].value - class_name = armor_lookup_dict[armor_class] - - armor_ref = f"{ability_ref}.{class_name}" - armor_raw_api_object = RawAPIObject(armor_ref, class_name, dataset.nyan_api_objects) - armor_raw_api_object.add_raw_parent(armor_parent) - armor_location = ForwardRef(line, ability_ref) - armor_raw_api_object.set_location(armor_location) - - # Type - type_ref = f"util.attribute_change_type.types.{class_name}" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - armor_raw_api_object.add_raw_member("type", - change_type, - resistance_parent) - - # Block value - # ================================================================================= - amount_name = f"{ability_ref}.{class_name}.BlockAmount" - amount_raw_api_object = RawAPIObject( - amount_name, "BlockAmount", dataset.nyan_api_objects) - amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") - amount_location = ForwardRef(line, armor_ref) - amount_raw_api_object.set_location(amount_location) - - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - amount_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeAmount") - amount_raw_api_object.add_raw_member("amount", - armor_amount, - "engine.util.attribute.AttributeAmount") - - line.add_raw_api_object(amount_raw_api_object) - # ================================================================================= - amount_forward_ref = ForwardRef(line, amount_name) - armor_raw_api_object.add_raw_member("block_value", - amount_forward_ref, - resistance_parent) - - line.add_raw_api_object(armor_raw_api_object) - armor_forward_ref = ForwardRef(line, armor_ref) - resistances.append(armor_forward_ref) - - # Fallback effect - fallback_effect = dataset.pregen_nyan_objects[("resistance.discrete.flat_attribute_change." - "fallback.AoE2AttackFallback")].get_nyan_object() - resistances.append(fallback_effect) - - return resistances - - @staticmethod - def get_convert_resistances( - line: GenieGameEntityGroup, - ability_ref: str - ) -> list[ForwardRef]: - """ - Creates resistances that are used for conversion (unit command: 104) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - dataset = line.data - - resistances = [] - - # AoE2Convert - resistance_parent = "engine.resistance.discrete.convert.Convert" - convert_parent = "engine.resistance.discrete.convert.type.AoE2Convert" - - resistance_ref = f"{ability_ref}.Convert" - resistance_raw_api_object = RawAPIObject( - resistance_ref, "Convert", dataset.nyan_api_objects) - resistance_raw_api_object.add_raw_parent(convert_parent) - resistance_location = ForwardRef(line, ability_ref) - resistance_raw_api_object.set_location(resistance_location) - - # Type - if isinstance(line, GenieUnitLineGroup): - type_ref = "util.convert_type.types.UnitConvert" - - else: - type_ref = "util.convert_type.types.BuildingConvert" - - convert_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - resistance_raw_api_object.add_raw_member("type", - convert_type, - resistance_parent) - - # Chance resist - # hardcoded resource - chance_resist = dataset.genie_civs[0]["resources"][77].value / 100 - resistance_raw_api_object.add_raw_member("chance_resist", - chance_resist, - resistance_parent) - - if isinstance(line, GenieUnitLineGroup): - guaranteed_rounds = dataset.genie_civs[0]["resources"][178].value - protected_rounds = dataset.genie_civs[0]["resources"][179].value - - else: - guaranteed_rounds = dataset.genie_civs[0]["resources"][180].value - protected_rounds = dataset.genie_civs[0]["resources"][181].value - - # Guaranteed rounds - resistance_raw_api_object.add_raw_member("guaranteed_resist_rounds", - guaranteed_rounds, - convert_parent) - - # Protected rounds - resistance_raw_api_object.add_raw_member("protected_rounds", - protected_rounds, - convert_parent) - - # Protection recharge - resistance_raw_api_object.add_raw_member("protection_round_recharge_time", - 0.0, - convert_parent) - - line.add_raw_api_object(resistance_raw_api_object) - resistance_forward_ref = ForwardRef(line, resistance_ref) - resistances.append(resistance_forward_ref) - - return resistances - - @staticmethod - def get_heal_resistances( - line: GenieGameEntityGroup, - ability_ref: str - ) -> list[ForwardRef]: - """ - Creates resistances that are used for healing (unit command: 105) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - dataset = line.data - - resistances = [] - - resistance_parent = "engine.resistance.continuous.flat_attribute_change.FlatAttributeChange" - heal_parent = "engine.resistance.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease" - - resistance_ref = f"{ability_ref}.Heal" - resistance_raw_api_object = RawAPIObject(resistance_ref, - "Heal", - dataset.nyan_api_objects) - resistance_raw_api_object.add_raw_parent(heal_parent) - resistance_location = ForwardRef(line, ability_ref) - resistance_raw_api_object.set_location(resistance_location) - - # Type - type_ref = "util.attribute_change_type.types.Heal" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - resistance_raw_api_object.add_raw_member("type", - change_type, - resistance_parent) - - # Block rate - # ================================================================================= - rate_name = f"{ability_ref}.Heal.BlockRate" - rate_raw_api_object = RawAPIObject(rate_name, "BlockRate", dataset.nyan_api_objects) - rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") - rate_location = ForwardRef(line, resistance_ref) - rate_raw_api_object.set_location(rate_location) - - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - rate_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeRate") - rate_raw_api_object.add_raw_member("rate", - 0.0, - "engine.util.attribute.AttributeRate") - - line.add_raw_api_object(rate_raw_api_object) - # ================================================================================= - rate_forward_ref = ForwardRef(line, rate_name) - resistance_raw_api_object.add_raw_member("block_rate", - rate_forward_ref, - resistance_parent) - - line.add_raw_api_object(resistance_raw_api_object) - resistance_forward_ref = ForwardRef(line, resistance_ref) - resistances.append(resistance_forward_ref) - - return resistances - - @staticmethod - def get_repair_resistances( - line: GenieGameEntityGroup, - ability_ref: str - ) -> list[ForwardRef]: - """ - Creates resistances that are used for repairing (unit command: 106) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - resistances = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - resistance_parent = "engine.resistance.continuous.flat_attribute_change.FlatAttributeChange" - repair_parent = "engine.resistance.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease" - - resistance_ref = f"{ability_ref}.Repair" - resistance_raw_api_object = RawAPIObject(resistance_ref, - "Repair", - dataset.nyan_api_objects) - resistance_raw_api_object.add_raw_parent(repair_parent) - resistance_location = ForwardRef(line, ability_ref) - resistance_raw_api_object.set_location(resistance_location) - - # Type - type_ref = f"util.attribute_change_type.types.{game_entity_name}Repair" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - resistance_raw_api_object.add_raw_member("type", - change_type, - resistance_parent) - - # Block rate - # ================================================================================= - rate_name = f"{ability_ref}.Repair.BlockRate" - rate_raw_api_object = RawAPIObject(rate_name, "BlockRate", dataset.nyan_api_objects) - rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") - rate_location = ForwardRef(line, resistance_ref) - rate_raw_api_object.set_location(rate_location) - - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - rate_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeRate") - rate_raw_api_object.add_raw_member("rate", - 0.0, - "engine.util.attribute.AttributeRate") - - line.add_raw_api_object(rate_raw_api_object) - # ================================================================================= - rate_forward_ref = ForwardRef(line, rate_name) - resistance_raw_api_object.add_raw_member("block_rate", - rate_forward_ref, - resistance_parent) - - # Stacking of villager repair HP increase - construct_property = dataset.pregen_nyan_objects["resistance.property.types.BuildingRepair"].get_nyan_object( - ) - properties = { - api_objects["engine.resistance.property.type.Stacked"]: construct_property - } - - # Add the predefined property - resistance_raw_api_object.add_raw_member("properties", - properties, - "engine.resistance.Resistance") - - line.add_raw_api_object(resistance_raw_api_object) - resistance_forward_ref = ForwardRef(line, resistance_ref) - resistances.append(resistance_forward_ref) - - return resistances - - @staticmethod - def get_construct_resistances( - line: GenieGameEntityGroup, - ability_ref: str - ) -> list[ForwardRef]: - """ - Creates resistances that are used for constructing (unit command: 101) - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - resistances = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - progress_resistance_parent = "engine.resistance.continuous.time_relative_progress.TimeRelativeProgressChange" - progress_construct_parent = "engine.resistance.continuous.time_relative_progress.type.TimeRelativeProgressIncrease" - attr_resistance_parent = "engine.resistance.continuous.time_relative_attribute.TimeRelativeAttributeChange" - attr_construct_parent = "engine.resistance.continuous.time_relative_attribute.type.TimeRelativeAttributeIncrease" - - # Progress - resistance_ref = f"{ability_ref}.ConstructProgress" - resistance_raw_api_object = RawAPIObject(resistance_ref, - "ConstructProgress", - dataset.nyan_api_objects) - resistance_raw_api_object.add_raw_parent(progress_construct_parent) - resistance_location = ForwardRef(line, ability_ref) - resistance_raw_api_object.set_location(resistance_location) - - # Type - type_ref = f"util.construct_type.types.{game_entity_name}Construct" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - resistance_raw_api_object.add_raw_member("type", - change_type, - progress_resistance_parent) - - line.add_raw_api_object(resistance_raw_api_object) - resistance_forward_ref = ForwardRef(line, resistance_ref) - resistances.append(resistance_forward_ref) - - # Stacking of villager construction times - construct_property = dataset.pregen_nyan_objects["resistance.property.types.BuildingConstruct"].get_nyan_object( - ) - properties = { - api_objects["engine.resistance.property.type.Stacked"]: construct_property - } - - # Add the predefined property - resistance_raw_api_object.add_raw_member("properties", - properties, - "engine.resistance.Resistance") - - # Health - resistance_ref = f"{ability_ref}.ConstructHP" - resistance_raw_api_object = RawAPIObject(resistance_ref, - "ConstructHP", - dataset.nyan_api_objects) - resistance_raw_api_object.add_raw_parent(attr_construct_parent) - resistance_location = ForwardRef(line, ability_ref) - resistance_raw_api_object.set_location(resistance_location) - - # Type - type_ref = f"util.attribute_change_type.types.{game_entity_name}Construct" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - resistance_raw_api_object.add_raw_member("type", - change_type, - attr_resistance_parent) - - # Stacking of villager construction HP increase - construct_property = dataset.pregen_nyan_objects["resistance.property.types.BuildingConstruct"].get_nyan_object( - ) - properties = { - api_objects["engine.resistance.property.type.Stacked"]: construct_property - } - - # Add the predefined property - resistance_raw_api_object.add_raw_member("properties", - properties, - "engine.resistance.Resistance") - - line.add_raw_api_object(resistance_raw_api_object) - resistance_forward_ref = ForwardRef(line, resistance_ref) - resistances.append(resistance_forward_ref) - - return resistances + get_attack_effects = staticmethod(get_attack_effects) + get_construct_effects = staticmethod(get_construct_effects) + get_convert_effects = staticmethod(get_convert_effects) + get_heal_effects = staticmethod(get_heal_effects) + get_repair_effects = staticmethod(get_repair_effects) + + get_attack_resistances = staticmethod(get_attack_resistances) + get_construct_resistances = staticmethod(get_construct_resistances) + get_convert_resistances = staticmethod(get_convert_resistances) + get_heal_resistances = staticmethod(get_heal_resistances) + get_repair_resistances = staticmethod(get_repair_resistances) diff --git a/openage/convert/processor/conversion/aoc/resistance/CMakeLists.txt b/openage/convert/processor/conversion/aoc/resistance/CMakeLists.txt new file mode 100644 index 0000000000..bd76227364 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/CMakeLists.txt @@ -0,0 +1,8 @@ +add_py_modules( + __init__.py + attack.py + construct.py + convert.py + heal.py + repair.py +) diff --git a/openage/convert/processor/conversion/aoc/resistance/__init__.py b/openage/convert/processor/conversion/aoc/resistance/__init__.py new file mode 100644 index 0000000000..5ff2e92839 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates resistances for the Resistance ability. +""" diff --git a/openage/convert/processor/conversion/aoc/resistance/attack.py b/openage/convert/processor/conversion/aoc/resistance/attack.py new file mode 100644 index 0000000000..6aa10bce85 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/attack.py @@ -0,0 +1,100 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create resistances for attacking units and buildings. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_attack_resistances( + line: GenieGameEntityGroup, + ability_ref: str +) -> list[ForwardRef]: + """ + Creates resistances that are used for attacking (unit command: 7) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param ability_ref: Reference of the ability raw API object the effects are added to. + :type ability_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + current_unit = line.get_head_unit() + dataset = line.data + + armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) + + resistances = [] + + # FlatAttributeChangeDecrease + resistance_parent = "engine.resistance.discrete.flat_attribute_change.FlatAttributeChange" + armor_parent = "engine.resistance.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" + + if current_unit.has_member("armors"): + armors = current_unit["armors"].value + + else: + # TODO: Trees and blast defense + armors = {} + + for armor in armors.values(): + armor_class = armor["type_id"].value + armor_amount = armor["amount"].value + class_name = armor_lookup_dict[armor_class] + + armor_ref = f"{ability_ref}.{class_name}" + armor_raw_api_object = RawAPIObject(armor_ref, class_name, dataset.nyan_api_objects) + armor_raw_api_object.add_raw_parent(armor_parent) + armor_location = ForwardRef(line, ability_ref) + armor_raw_api_object.set_location(armor_location) + + # Type + type_ref = f"util.attribute_change_type.types.{class_name}" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + armor_raw_api_object.add_raw_member("type", + change_type, + resistance_parent) + + # Block value + # ================================================================================= + amount_name = f"{ability_ref}.{class_name}.BlockAmount" + amount_raw_api_object = RawAPIObject( + amount_name, "BlockAmount", dataset.nyan_api_objects) + amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") + amount_location = ForwardRef(line, armor_ref) + amount_raw_api_object.set_location(amount_location) + + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + amount_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeAmount") + amount_raw_api_object.add_raw_member("amount", + armor_amount, + "engine.util.attribute.AttributeAmount") + + line.add_raw_api_object(amount_raw_api_object) + # ================================================================================= + amount_forward_ref = ForwardRef(line, amount_name) + armor_raw_api_object.add_raw_member("block_value", + amount_forward_ref, + resistance_parent) + + line.add_raw_api_object(armor_raw_api_object) + armor_forward_ref = ForwardRef(line, armor_ref) + resistances.append(armor_forward_ref) + + # Fallback effect + fallback_effect = dataset.pregen_nyan_objects[("resistance.discrete.flat_attribute_change." + "fallback.AoE2AttackFallback")].get_nyan_object() + resistances.append(fallback_effect) + + return resistances diff --git a/openage/convert/processor/conversion/aoc/resistance/construct.py b/openage/convert/processor/conversion/aoc/resistance/construct.py new file mode 100644 index 0000000000..b05843f78b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/construct.py @@ -0,0 +1,110 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create resistances for constructing buildings. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_construct_resistances( + line: GenieGameEntityGroup, + ability_ref: str +) -> list[ForwardRef]: + """ + Creates resistances that are used for constructing (unit command: 101) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param ability_ref: Reference of the ability raw API object the effects are added to. + :type ability_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + resistances = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + progress_resistance_parent = "engine.resistance.continuous.time_relative_progress.TimeRelativeProgressChange" + progress_construct_parent = "engine.resistance.continuous.time_relative_progress.type.TimeRelativeProgressIncrease" + attr_resistance_parent = "engine.resistance.continuous.time_relative_attribute.TimeRelativeAttributeChange" + attr_construct_parent = "engine.resistance.continuous.time_relative_attribute.type.TimeRelativeAttributeIncrease" + + # Progress + resistance_ref = f"{ability_ref}.ConstructProgress" + resistance_raw_api_object = RawAPIObject(resistance_ref, + "ConstructProgress", + dataset.nyan_api_objects) + resistance_raw_api_object.add_raw_parent(progress_construct_parent) + resistance_location = ForwardRef(line, ability_ref) + resistance_raw_api_object.set_location(resistance_location) + + # Type + type_ref = f"util.construct_type.types.{game_entity_name}Construct" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + resistance_raw_api_object.add_raw_member("type", + change_type, + progress_resistance_parent) + + line.add_raw_api_object(resistance_raw_api_object) + resistance_forward_ref = ForwardRef(line, resistance_ref) + resistances.append(resistance_forward_ref) + + # Stacking of villager construction times + construct_property = dataset.pregen_nyan_objects["resistance.property.types.BuildingConstruct"].get_nyan_object( + ) + properties = { + api_objects["engine.resistance.property.type.Stacked"]: construct_property + } + + # Add the predefined property + resistance_raw_api_object.add_raw_member("properties", + properties, + "engine.resistance.Resistance") + + # Health + resistance_ref = f"{ability_ref}.ConstructHP" + resistance_raw_api_object = RawAPIObject(resistance_ref, + "ConstructHP", + dataset.nyan_api_objects) + resistance_raw_api_object.add_raw_parent(attr_construct_parent) + resistance_location = ForwardRef(line, ability_ref) + resistance_raw_api_object.set_location(resistance_location) + + # Type + type_ref = f"util.attribute_change_type.types.{game_entity_name}Construct" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + resistance_raw_api_object.add_raw_member("type", + change_type, + attr_resistance_parent) + + # Stacking of villager construction HP increase + construct_property = dataset.pregen_nyan_objects["resistance.property.types.BuildingConstruct"].get_nyan_object( + ) + properties = { + api_objects["engine.resistance.property.type.Stacked"]: construct_property + } + + # Add the predefined property + resistance_raw_api_object.add_raw_member("properties", + properties, + "engine.resistance.Resistance") + + line.add_raw_api_object(resistance_raw_api_object) + resistance_forward_ref = ForwardRef(line, resistance_ref) + resistances.append(resistance_forward_ref) + + return resistances diff --git a/openage/convert/processor/conversion/aoc/resistance/convert.py b/openage/convert/processor/conversion/aoc/resistance/convert.py new file mode 100644 index 0000000000..4c87a72430 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/convert.py @@ -0,0 +1,93 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create resistances for converting units and buildings. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +@staticmethod +def get_convert_resistances( + line: GenieGameEntityGroup, + ability_ref: str +) -> list[ForwardRef]: + """ + Creates resistances that are used for conversion (unit command: 104) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param ability_ref: Reference of the ability raw API object the effects are added to. + :type ability_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + dataset = line.data + + resistances = [] + + # AoE2Convert + resistance_parent = "engine.resistance.discrete.convert.Convert" + convert_parent = "engine.resistance.discrete.convert.type.AoE2Convert" + + resistance_ref = f"{ability_ref}.Convert" + resistance_raw_api_object = RawAPIObject( + resistance_ref, "Convert", dataset.nyan_api_objects) + resistance_raw_api_object.add_raw_parent(convert_parent) + resistance_location = ForwardRef(line, ability_ref) + resistance_raw_api_object.set_location(resistance_location) + + # Type + if isinstance(line, GenieUnitLineGroup): + type_ref = "util.convert_type.types.UnitConvert" + + else: + type_ref = "util.convert_type.types.BuildingConvert" + + convert_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + resistance_raw_api_object.add_raw_member("type", + convert_type, + resistance_parent) + + # Chance resist + # hardcoded resource + chance_resist = dataset.genie_civs[0]["resources"][77].value / 100 + resistance_raw_api_object.add_raw_member("chance_resist", + chance_resist, + resistance_parent) + + if isinstance(line, GenieUnitLineGroup): + guaranteed_rounds = dataset.genie_civs[0]["resources"][178].value + protected_rounds = dataset.genie_civs[0]["resources"][179].value + + else: + guaranteed_rounds = dataset.genie_civs[0]["resources"][180].value + protected_rounds = dataset.genie_civs[0]["resources"][181].value + + # Guaranteed rounds + resistance_raw_api_object.add_raw_member("guaranteed_resist_rounds", + guaranteed_rounds, + convert_parent) + + # Protected rounds + resistance_raw_api_object.add_raw_member("protected_rounds", + protected_rounds, + convert_parent) + + # Protection recharge + resistance_raw_api_object.add_raw_member("protection_round_recharge_time", + 0.0, + convert_parent) + + line.add_raw_api_object(resistance_raw_api_object) + resistance_forward_ref = ForwardRef(line, resistance_ref) + resistances.append(resistance_forward_ref) + + return resistances diff --git a/openage/convert/processor/conversion/aoc/resistance/heal.py b/openage/convert/processor/conversion/aoc/resistance/heal.py new file mode 100644 index 0000000000..02f8feab5e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/heal.py @@ -0,0 +1,79 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create resistances for healing units. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_heal_resistances( + line: GenieGameEntityGroup, + ability_ref: str +) -> list[ForwardRef]: + """ + Creates resistances that are used for healing (unit command: 105) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param ability_ref: Reference of the ability raw API object the effects are added to. + :type ability_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + dataset = line.data + + resistances = [] + + resistance_parent = "engine.resistance.continuous.flat_attribute_change.FlatAttributeChange" + heal_parent = "engine.resistance.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease" + + resistance_ref = f"{ability_ref}.Heal" + resistance_raw_api_object = RawAPIObject(resistance_ref, + "Heal", + dataset.nyan_api_objects) + resistance_raw_api_object.add_raw_parent(heal_parent) + resistance_location = ForwardRef(line, ability_ref) + resistance_raw_api_object.set_location(resistance_location) + + # Type + type_ref = "util.attribute_change_type.types.Heal" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + resistance_raw_api_object.add_raw_member("type", + change_type, + resistance_parent) + + # Block rate + # ================================================================================= + rate_name = f"{ability_ref}.Heal.BlockRate" + rate_raw_api_object = RawAPIObject(rate_name, "BlockRate", dataset.nyan_api_objects) + rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") + rate_location = ForwardRef(line, resistance_ref) + rate_raw_api_object.set_location(rate_location) + + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + rate_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeRate") + rate_raw_api_object.add_raw_member("rate", + 0.0, + "engine.util.attribute.AttributeRate") + + line.add_raw_api_object(rate_raw_api_object) + # ================================================================================= + rate_forward_ref = ForwardRef(line, rate_name) + resistance_raw_api_object.add_raw_member("block_rate", + rate_forward_ref, + resistance_parent) + + line.add_raw_api_object(resistance_raw_api_object) + resistance_forward_ref = ForwardRef(line, resistance_ref) + resistances.append(resistance_forward_ref) + + return resistances diff --git a/openage/convert/processor/conversion/aoc/resistance/repair.py b/openage/convert/processor/conversion/aoc/resistance/repair.py new file mode 100644 index 0000000000..1bee078cff --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/repair.py @@ -0,0 +1,98 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create resistances for repairing buildings. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_repair_resistances( + line: GenieGameEntityGroup, + ability_ref: str +) -> list[ForwardRef]: + """ + Creates resistances that are used for repairing (unit command: 106) + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param ability_ref: Reference of the ability raw API object the effects are added to. + :type ability_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + resistances = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + resistance_parent = "engine.resistance.continuous.flat_attribute_change.FlatAttributeChange" + repair_parent = "engine.resistance.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease" + + resistance_ref = f"{ability_ref}.Repair" + resistance_raw_api_object = RawAPIObject(resistance_ref, + "Repair", + dataset.nyan_api_objects) + resistance_raw_api_object.add_raw_parent(repair_parent) + resistance_location = ForwardRef(line, ability_ref) + resistance_raw_api_object.set_location(resistance_location) + + # Type + type_ref = f"util.attribute_change_type.types.{game_entity_name}Repair" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + resistance_raw_api_object.add_raw_member("type", + change_type, + resistance_parent) + + # Block rate + # ================================================================================= + rate_name = f"{ability_ref}.Repair.BlockRate" + rate_raw_api_object = RawAPIObject(rate_name, "BlockRate", dataset.nyan_api_objects) + rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") + rate_location = ForwardRef(line, resistance_ref) + rate_raw_api_object.set_location(rate_location) + + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + rate_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeRate") + rate_raw_api_object.add_raw_member("rate", + 0.0, + "engine.util.attribute.AttributeRate") + + line.add_raw_api_object(rate_raw_api_object) + # ================================================================================= + rate_forward_ref = ForwardRef(line, rate_name) + resistance_raw_api_object.add_raw_member("block_rate", + rate_forward_ref, + resistance_parent) + + # Stacking of villager repair HP increase + construct_property = dataset.pregen_nyan_objects["resistance.property.types.BuildingRepair"].get_nyan_object( + ) + properties = { + api_objects["engine.resistance.property.type.Stacked"]: construct_property + } + + # Add the predefined property + resistance_raw_api_object.add_raw_member("properties", + properties, + "engine.resistance.Resistance") + + line.add_raw_api_object(resistance_raw_api_object) + resistance_forward_ref = ForwardRef(line, resistance_ref) + resistances.append(resistance_forward_ref) + + return resistances From 9da6b66d7a1de90ddd081e838e13da9a99b0f328 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 28 May 2025 07:25:10 +0200 Subject: [PATCH 111/163] convert: Remove typedefs from docstrings. These have been replaced by type hints in the function signature. --- .../aoc/ability/active_transform_to.py | 2 -- .../conversion/aoc/ability/activity.py | 2 -- .../aoc/ability/apply_continuous_effect.py | 2 -- .../aoc/ability/apply_discrete_effect.py | 2 -- .../aoc/ability/attribute_change_tracker.py | 2 -- .../conversion/aoc/ability/collect_storage.py | 2 -- .../conversion/aoc/ability/collision.py | 2 -- .../conversion/aoc/ability/constructable.py | 2 -- .../conversion/aoc/ability/create.py | 2 -- .../processor/conversion/aoc/ability/death.py | 2 -- .../conversion/aoc/ability/delete.py | 5 ++-- .../conversion/aoc/ability/despawn.py | 2 -- .../conversion/aoc/ability/drop_resources.py | 2 -- .../conversion/aoc/ability/drop_site.py | 2 -- .../conversion/aoc/ability/enter_container.py | 2 -- .../aoc/ability/exchange_resources.py | 2 -- .../conversion/aoc/ability/exit_container.py | 2 -- .../conversion/aoc/ability/formation.py | 2 -- .../conversion/aoc/ability/foundation.py | 3 --- .../aoc/ability/game_entity_stance.py | 2 -- .../conversion/aoc/ability/gather.py | 4 +--- .../conversion/aoc/ability/harvestable.py | 2 -- .../processor/conversion/aoc/ability/herd.py | 2 -- .../conversion/aoc/ability/herdable.py | 2 -- .../processor/conversion/aoc/ability/idle.py | 2 -- .../conversion/aoc/ability/line_of_sight.py | 2 -- .../processor/conversion/aoc/ability/live.py | 2 -- .../processor/conversion/aoc/ability/move.py | 4 ---- .../processor/conversion/aoc/ability/named.py | 2 -- .../conversion/aoc/ability/overlay_terrain.py | 2 -- .../conversion/aoc/ability/pathable.py | 2 -- .../aoc/ability/production_queue.py | 2 -- .../conversion/aoc/ability/projectile.py | 5 +--- .../aoc/ability/provide_contingent.py | 4 ---- .../conversion/aoc/ability/rally_point.py | 2 -- .../aoc/ability/regenerate_attribute.py | 2 -- .../aoc/ability/regenerate_resource_spot.py | 2 -- .../conversion/aoc/ability/remove_storage.py | 2 -- .../conversion/aoc/ability/research.py | 2 -- .../conversion/aoc/ability/resistance.py | 2 -- .../aoc/ability/resource_storage.py | 2 -- .../conversion/aoc/ability/restock.py | 2 -- .../conversion/aoc/ability/selectable.py | 2 -- .../aoc/ability/send_back_to_task.py | 2 -- .../aoc/ability/shoot_projectile.py | 2 -- .../processor/conversion/aoc/ability/stop.py | 2 -- .../conversion/aoc/ability/storage.py | 2 -- .../aoc/ability/terrain_requirement.py | 2 -- .../processor/conversion/aoc/ability/trade.py | 2 -- .../conversion/aoc/ability/trade_post.py | 2 -- .../aoc/ability/transfer_storage.py | 2 -- .../processor/conversion/aoc/ability/turn.py | 2 -- .../conversion/aoc/ability/use_contingent.py | 2 -- .../processor/conversion/aoc/ability/util.py | 23 +------------------ .../conversion/aoc/ability/visibility.py | 2 -- .../processor/conversion/aoc/effect/attack.py | 3 --- .../conversion/aoc/effect/construct.py | 3 --- .../conversion/aoc/effect/convert.py | 3 --- .../processor/conversion/aoc/effect/heal.py | 3 --- .../processor/conversion/aoc/effect/repair.py | 3 --- .../conversion/aoc/resistance/attack.py | 3 --- .../conversion/aoc/resistance/construct.py | 3 --- .../conversion/aoc/resistance/convert.py | 3 --- .../conversion/aoc/resistance/heal.py | 3 --- .../conversion/aoc/resistance/repair.py | 3 --- 65 files changed, 5 insertions(+), 169 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/ability/active_transform_to.py b/openage/convert/processor/conversion/aoc/ability/active_transform_to.py index 6236b54106..b007bb99f6 100644 --- a/openage/convert/processor/conversion/aoc/ability/active_transform_to.py +++ b/openage/convert/processor/conversion/aoc/ability/active_transform_to.py @@ -16,9 +16,7 @@ def active_transform_to_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the ActiveTransformTo 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 """ # TODO: Implement return None diff --git a/openage/convert/processor/conversion/aoc/ability/activity.py b/openage/convert/processor/conversion/aoc/ability/activity.py index e08509308b..826c2cd3ed 100644 --- a/openage/convert/processor/conversion/aoc/ability/activity.py +++ b/openage/convert/processor/conversion/aoc/ability/activity.py @@ -21,9 +21,7 @@ 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() diff --git a/openage/convert/processor/conversion/aoc/ability/apply_continuous_effect.py b/openage/convert/processor/conversion/aoc/ability/apply_continuous_effect.py index ae8d617ff7..9541d5598d 100644 --- a/openage/convert/processor/conversion/aoc/ability/apply_continuous_effect.py +++ b/openage/convert/processor/conversion/aoc/ability/apply_continuous_effect.py @@ -27,9 +27,7 @@ def apply_continuous_effect_ability( Adds the ApplyContinuousEffect 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 """ if isinstance(line, GenieVillagerGroup): current_unit = line.get_units_with_command(command_id)[0] diff --git a/openage/convert/processor/conversion/aoc/ability/apply_discrete_effect.py b/openage/convert/processor/conversion/aoc/ability/apply_discrete_effect.py index f1ee0eed9f..f61b87cbb9 100644 --- a/openage/convert/processor/conversion/aoc/ability/apply_discrete_effect.py +++ b/openage/convert/processor/conversion/aoc/ability/apply_discrete_effect.py @@ -28,9 +28,7 @@ def apply_discrete_effect_ability( Adds the ApplyDiscreteEffect 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 """ if isinstance(line, GenieVillagerGroup): current_unit = line.get_units_with_command(command_id)[0] diff --git a/openage/convert/processor/conversion/aoc/ability/attribute_change_tracker.py b/openage/convert/processor/conversion/aoc/ability/attribute_change_tracker.py index d1da844108..b9c1382798 100644 --- a/openage/convert/processor/conversion/aoc/ability/attribute_change_tracker.py +++ b/openage/convert/processor/conversion/aoc/ability/attribute_change_tracker.py @@ -21,9 +21,7 @@ def attribute_change_tracker_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the AttributeChangeTracker 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/collect_storage.py b/openage/convert/processor/conversion/aoc/ability/collect_storage.py index 654bcf4a86..710d68095d 100644 --- a/openage/convert/processor/conversion/aoc/ability/collect_storage.py +++ b/openage/convert/processor/conversion/aoc/ability/collect_storage.py @@ -20,9 +20,7 @@ def collect_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the CollectStorage 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 diff --git a/openage/convert/processor/conversion/aoc/ability/collision.py b/openage/convert/processor/conversion/aoc/ability/collision.py index 48c5c89e92..0915f1b2de 100644 --- a/openage/convert/processor/conversion/aoc/ability/collision.py +++ b/openage/convert/processor/conversion/aoc/ability/collision.py @@ -20,9 +20,7 @@ def collision_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Collision 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/constructable.py b/openage/convert/processor/conversion/aoc/ability/constructable.py index f3dbde9431..096c9c5e28 100644 --- a/openage/convert/processor/conversion/aoc/ability/constructable.py +++ b/openage/convert/processor/conversion/aoc/ability/constructable.py @@ -22,9 +22,7 @@ def constructable_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Constructable 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/create.py b/openage/convert/processor/conversion/aoc/ability/create.py index 3cd5de8f33..a298102095 100644 --- a/openage/convert/processor/conversion/aoc/ability/create.py +++ b/openage/convert/processor/conversion/aoc/ability/create.py @@ -20,9 +20,7 @@ def create_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Create 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 diff --git a/openage/convert/processor/conversion/aoc/ability/death.py b/openage/convert/processor/conversion/aoc/ability/death.py index f837ed994f..a06e126178 100644 --- a/openage/convert/processor/conversion/aoc/ability/death.py +++ b/openage/convert/processor/conversion/aoc/ability/death.py @@ -24,9 +24,7 @@ def death_ability(line: GenieGameEntityGroup) -> ForwardRef: based on a condition. :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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/delete.py b/openage/convert/processor/conversion/aoc/ability/delete.py index cc7b316b7f..f146f34bbe 100644 --- a/openage/convert/processor/conversion/aoc/ability/delete.py +++ b/openage/convert/processor/conversion/aoc/ability/delete.py @@ -17,12 +17,11 @@ def delete_ability(line: GenieGameEntityGroup) -> ForwardRef: """ - Adds a PassiveTransformTo ability to a line that is used to make entities deletable. + Adds a PassiveTransformTo ability to a line that is used to make entities deletable, + i.e. die on command. :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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/despawn.py b/openage/convert/processor/conversion/aoc/ability/despawn.py index 6e9319cc25..faf67bc621 100644 --- a/openage/convert/processor/conversion/aoc/ability/despawn.py +++ b/openage/convert/processor/conversion/aoc/ability/despawn.py @@ -21,9 +21,7 @@ def despawn_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Despawn 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/drop_resources.py b/openage/convert/processor/conversion/aoc/ability/drop_resources.py index 85adc849a6..c84cd92b73 100644 --- a/openage/convert/processor/conversion/aoc/ability/drop_resources.py +++ b/openage/convert/processor/conversion/aoc/ability/drop_resources.py @@ -23,9 +23,7 @@ def drop_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the DropResources 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 """ if isinstance(line, GenieVillagerGroup): gatherers = line.variants[0].line diff --git a/openage/convert/processor/conversion/aoc/ability/drop_site.py b/openage/convert/processor/conversion/aoc/ability/drop_site.py index 68f4851262..3d08e6b827 100644 --- a/openage/convert/processor/conversion/aoc/ability/drop_site.py +++ b/openage/convert/processor/conversion/aoc/ability/drop_site.py @@ -20,9 +20,7 @@ def drop_site_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the DropSite 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 diff --git a/openage/convert/processor/conversion/aoc/ability/enter_container.py b/openage/convert/processor/conversion/aoc/ability/enter_container.py index 3efa7fe651..9e6dd6d4b7 100644 --- a/openage/convert/processor/conversion/aoc/ability/enter_container.py +++ b/openage/convert/processor/conversion/aoc/ability/enter_container.py @@ -21,9 +21,7 @@ def enter_container_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the EnterContainer 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. None if no valid containers were found. - :rtype: ...dataformat.forward_ref.ForwardRef, None """ current_unit_id = line.get_head_unit_id() dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/ability/exchange_resources.py b/openage/convert/processor/conversion/aoc/ability/exchange_resources.py index 2dc2f0383f..ad8852c2f8 100644 --- a/openage/convert/processor/conversion/aoc/ability/exchange_resources.py +++ b/openage/convert/processor/conversion/aoc/ability/exchange_resources.py @@ -20,9 +20,7 @@ def exchange_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the ExchangeResources 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 diff --git a/openage/convert/processor/conversion/aoc/ability/exit_container.py b/openage/convert/processor/conversion/aoc/ability/exit_container.py index f5065ee5ea..95d76fb0c2 100644 --- a/openage/convert/processor/conversion/aoc/ability/exit_container.py +++ b/openage/convert/processor/conversion/aoc/ability/exit_container.py @@ -21,9 +21,7 @@ def exit_container_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the ExitContainer 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. None if no valid containers were found. - :rtype: ...dataformat.forward_ref.ForwardRef, None """ current_unit_id = line.get_head_unit_id() dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/ability/formation.py b/openage/convert/processor/conversion/aoc/ability/formation.py index 718fa487e6..8e1c0e3f9a 100644 --- a/openage/convert/processor/conversion/aoc/ability/formation.py +++ b/openage/convert/processor/conversion/aoc/ability/formation.py @@ -20,9 +20,7 @@ def formation_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Formation 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 diff --git a/openage/convert/processor/conversion/aoc/ability/foundation.py b/openage/convert/processor/conversion/aoc/ability/foundation.py index f46c04a271..3c2dbb0969 100644 --- a/openage/convert/processor/conversion/aoc/ability/foundation.py +++ b/openage/convert/processor/conversion/aoc/ability/foundation.py @@ -21,11 +21,8 @@ def foundation_ability(line: GenieGameEntityGroup, terrain_id: int = -1) -> Forw terrain ID. :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param terrain_id: Force this terrain ID as foundation - :type terrain_id: int :returns: The forward references for the abilities. - :rtype: ...dataformat.forward_ref.ForwardRef """ current_unit = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/game_entity_stance.py b/openage/convert/processor/conversion/aoc/ability/game_entity_stance.py index 0a85843614..4f1133fa9b 100644 --- a/openage/convert/processor/conversion/aoc/ability/game_entity_stance.py +++ b/openage/convert/processor/conversion/aoc/ability/game_entity_stance.py @@ -20,9 +20,7 @@ def game_entity_stance_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the GameEntityStance 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/gather.py b/openage/convert/processor/conversion/aoc/ability/gather.py index 6a23d73ae8..acb2b89ce3 100644 --- a/openage/convert/processor/conversion/aoc/ability/gather.py +++ b/openage/convert/processor/conversion/aoc/ability/gather.py @@ -19,15 +19,13 @@ from .....value_object.conversion.forward_ref import ForwardRef -def gather_ability(line: GenieGameEntityGroup) -> ForwardRef: +def gather_ability(line: GenieGameEntityGroup) -> list[ForwardRef]: """ Adds the Gather abilities to a line. Unlike the other methods, this creates multiple abilities. :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :returns: The forward references for the abilities. - :rtype: list """ if isinstance(line, GenieVillagerGroup): gatherers = line.variants[0].line diff --git a/openage/convert/processor/conversion/aoc/ability/harvestable.py b/openage/convert/processor/conversion/aoc/ability/harvestable.py index 8e312b7f75..065d40e6ae 100644 --- a/openage/convert/processor/conversion/aoc/ability/harvestable.py +++ b/openage/convert/processor/conversion/aoc/ability/harvestable.py @@ -21,9 +21,7 @@ def harvestable_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Harvestable 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/herd.py b/openage/convert/processor/conversion/aoc/ability/herd.py index 1f71f0c6ca..b4a9ac7db4 100644 --- a/openage/convert/processor/conversion/aoc/ability/herd.py +++ b/openage/convert/processor/conversion/aoc/ability/herd.py @@ -20,9 +20,7 @@ def herd_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Herd 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 diff --git a/openage/convert/processor/conversion/aoc/ability/herdable.py b/openage/convert/processor/conversion/aoc/ability/herdable.py index 80b28ebbb3..804913f9c5 100644 --- a/openage/convert/processor/conversion/aoc/ability/herdable.py +++ b/openage/convert/processor/conversion/aoc/ability/herdable.py @@ -20,9 +20,7 @@ def herdable_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Herdable 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 diff --git a/openage/convert/processor/conversion/aoc/ability/idle.py b/openage/convert/processor/conversion/aoc/ability/idle.py index 36ce28af41..3954fe3edd 100644 --- a/openage/convert/processor/conversion/aoc/ability/idle.py +++ b/openage/convert/processor/conversion/aoc/ability/idle.py @@ -21,9 +21,7 @@ def idle_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Idle 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/line_of_sight.py b/openage/convert/processor/conversion/aoc/ability/line_of_sight.py index ffe589e28f..c836c5f7d4 100644 --- a/openage/convert/processor/conversion/aoc/ability/line_of_sight.py +++ b/openage/convert/processor/conversion/aoc/ability/line_of_sight.py @@ -20,9 +20,7 @@ def line_of_sight_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the LineOfSight 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/live.py b/openage/convert/processor/conversion/aoc/ability/live.py index 6c72b17794..5505dc881c 100644 --- a/openage/convert/processor/conversion/aoc/ability/live.py +++ b/openage/convert/processor/conversion/aoc/ability/live.py @@ -21,9 +21,7 @@ def live_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Live 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/move.py b/openage/convert/processor/conversion/aoc/ability/move.py index 3b3dd5ede8..7da2671d6d 100644 --- a/openage/convert/processor/conversion/aoc/ability/move.py +++ b/openage/convert/processor/conversion/aoc/ability/move.py @@ -21,9 +21,7 @@ def move_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Move 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() @@ -220,9 +218,7 @@ def move_projectile_ability(line: GenieGameEntityGroup, position: int = -1) -> F Adds the Move ability to a projectile of the specified 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 """ dataset = line.data api_objects = dataset.nyan_api_objects diff --git a/openage/convert/processor/conversion/aoc/ability/named.py b/openage/convert/processor/conversion/aoc/ability/named.py index 71be7959db..cfe9d088bb 100644 --- a/openage/convert/processor/conversion/aoc/ability/named.py +++ b/openage/convert/processor/conversion/aoc/ability/named.py @@ -21,9 +21,7 @@ def named_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Named 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/overlay_terrain.py b/openage/convert/processor/conversion/aoc/ability/overlay_terrain.py index 264015e9fb..572a151cb4 100644 --- a/openage/convert/processor/conversion/aoc/ability/overlay_terrain.py +++ b/openage/convert/processor/conversion/aoc/ability/overlay_terrain.py @@ -20,9 +20,7 @@ def overlay_terrain_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the OverlayTerrain to a line. :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :returns: The forward references for the abilities. - :rtype: ...dataformat.forward_ref.ForwardRef """ current_unit = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/pathable.py b/openage/convert/processor/conversion/aoc/ability/pathable.py index 8aba68125c..26a576ef5b 100644 --- a/openage/convert/processor/conversion/aoc/ability/pathable.py +++ b/openage/convert/processor/conversion/aoc/ability/pathable.py @@ -20,9 +20,7 @@ def pathable_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Pathable 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 diff --git a/openage/convert/processor/conversion/aoc/ability/production_queue.py b/openage/convert/processor/conversion/aoc/ability/production_queue.py index 0a60ecca00..c255e6b058 100644 --- a/openage/convert/processor/conversion/aoc/ability/production_queue.py +++ b/openage/convert/processor/conversion/aoc/ability/production_queue.py @@ -20,9 +20,7 @@ def production_queue_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the ProductionQueue 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 diff --git a/openage/convert/processor/conversion/aoc/ability/projectile.py b/openage/convert/processor/conversion/aoc/ability/projectile.py index db16695657..82c305fdf9 100644 --- a/openage/convert/processor/conversion/aoc/ability/projectile.py +++ b/openage/convert/processor/conversion/aoc/ability/projectile.py @@ -23,11 +23,8 @@ def projectile_ability(line: GenieGameEntityGroup, position: int = 0) -> Forward be added is determined by the 'position' argument. :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param position: When 0, gives the first projectile its ability. When 1, the second... - :type position: int + :param position: Index of the projectile to add (0 or 1 for AoC). :returns: The forward reference for the ability. - :rtype: ...dataformat.forward_ref.ForwardRef """ current_unit = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/provide_contingent.py b/openage/convert/processor/conversion/aoc/ability/provide_contingent.py index 816d6ae392..ba220c91e7 100644 --- a/openage/convert/processor/conversion/aoc/ability/provide_contingent.py +++ b/openage/convert/processor/conversion/aoc/ability/provide_contingent.py @@ -15,17 +15,13 @@ from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup from .....value_object.conversion.forward_ref import ForwardRef -# ASDF: remove type hints in docstring - def provide_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the ProvideContingent 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 = line.get_head_unit() if isinstance(line, GenieStackBuildingGroup): diff --git a/openage/convert/processor/conversion/aoc/ability/rally_point.py b/openage/convert/processor/conversion/aoc/ability/rally_point.py index 49fe4f078e..3ae74e506d 100644 --- a/openage/convert/processor/conversion/aoc/ability/rally_point.py +++ b/openage/convert/processor/conversion/aoc/ability/rally_point.py @@ -20,9 +20,7 @@ def rally_point_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the RallyPoint 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 diff --git a/openage/convert/processor/conversion/aoc/ability/regenerate_attribute.py b/openage/convert/processor/conversion/aoc/ability/regenerate_attribute.py index d0c74ef95a..2d24e3a585 100644 --- a/openage/convert/processor/conversion/aoc/ability/regenerate_attribute.py +++ b/openage/convert/processor/conversion/aoc/ability/regenerate_attribute.py @@ -20,9 +20,7 @@ def regenerate_attribute_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the RegenerateAttribute ability to a line. :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :returns: The forward references for the ability. - :rtype: list """ current_unit_id = line.get_head_unit_id() dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/ability/regenerate_resource_spot.py b/openage/convert/processor/conversion/aoc/ability/regenerate_resource_spot.py index 942707b0cd..a0d3f4a13f 100644 --- a/openage/convert/processor/conversion/aoc/ability/regenerate_resource_spot.py +++ b/openage/convert/processor/conversion/aoc/ability/regenerate_resource_spot.py @@ -16,9 +16,7 @@ def regenerate_resource_spot_ability(line: GenieGameEntityGroup) -> None: Adds the RegenerateResourceSpot 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 """ # Unused in AoC return None diff --git a/openage/convert/processor/conversion/aoc/ability/remove_storage.py b/openage/convert/processor/conversion/aoc/ability/remove_storage.py index b4413dfe9e..99702de361 100644 --- a/openage/convert/processor/conversion/aoc/ability/remove_storage.py +++ b/openage/convert/processor/conversion/aoc/ability/remove_storage.py @@ -20,9 +20,7 @@ def remove_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the RemoveStorage 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 diff --git a/openage/convert/processor/conversion/aoc/ability/research.py b/openage/convert/processor/conversion/aoc/ability/research.py index 3dce8646d4..c93b322bdd 100644 --- a/openage/convert/processor/conversion/aoc/ability/research.py +++ b/openage/convert/processor/conversion/aoc/ability/research.py @@ -20,9 +20,7 @@ def research_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Research 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 diff --git a/openage/convert/processor/conversion/aoc/ability/resistance.py b/openage/convert/processor/conversion/aoc/ability/resistance.py index aec32cd1fc..b1002d13c4 100644 --- a/openage/convert/processor/conversion/aoc/ability/resistance.py +++ b/openage/convert/processor/conversion/aoc/ability/resistance.py @@ -22,9 +22,7 @@ def resistance_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Resistance 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 diff --git a/openage/convert/processor/conversion/aoc/ability/resource_storage.py b/openage/convert/processor/conversion/aoc/ability/resource_storage.py index 59abedd4f5..df647be8b8 100644 --- a/openage/convert/processor/conversion/aoc/ability/resource_storage.py +++ b/openage/convert/processor/conversion/aoc/ability/resource_storage.py @@ -23,9 +23,7 @@ def resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the ResourceStorage 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 """ if isinstance(line, GenieVillagerGroup): gatherers = line.variants[0].line diff --git a/openage/convert/processor/conversion/aoc/ability/restock.py b/openage/convert/processor/conversion/aoc/ability/restock.py index 9093f4bb89..216381a822 100644 --- a/openage/convert/processor/conversion/aoc/ability/restock.py +++ b/openage/convert/processor/conversion/aoc/ability/restock.py @@ -22,9 +22,7 @@ def restock_ability(line: GenieGameEntityGroup, restock_target_id: int) -> Forwa Adds the Restock 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 diff --git a/openage/convert/processor/conversion/aoc/ability/selectable.py b/openage/convert/processor/conversion/aoc/ability/selectable.py index 77113ad9f2..2be194641c 100644 --- a/openage/convert/processor/conversion/aoc/ability/selectable.py +++ b/openage/convert/processor/conversion/aoc/ability/selectable.py @@ -24,9 +24,7 @@ def selectable_ability(line: GenieGameEntityGroup) -> ForwardRef: for other stances. :param line: Unit/Building line that gets the abilities. - :type line: ...dataformat.converter_object.ConverterObjectGroup :returns: The forward reference for the abilities. - :rtype: ...dataformat.forward_ref.ForwardRef """ current_unit = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/send_back_to_task.py b/openage/convert/processor/conversion/aoc/ability/send_back_to_task.py index a7f4686ef1..0eca12fa47 100644 --- a/openage/convert/processor/conversion/aoc/ability/send_back_to_task.py +++ b/openage/convert/processor/conversion/aoc/ability/send_back_to_task.py @@ -20,9 +20,7 @@ def send_back_to_task_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the SendBackToTask 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 diff --git a/openage/convert/processor/conversion/aoc/ability/shoot_projectile.py b/openage/convert/processor/conversion/aoc/ability/shoot_projectile.py index 63ad9f0e61..a31eade77b 100644 --- a/openage/convert/processor/conversion/aoc/ability/shoot_projectile.py +++ b/openage/convert/processor/conversion/aoc/ability/shoot_projectile.py @@ -22,9 +22,7 @@ def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> For Adds the ShootProjectile 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/stop.py b/openage/convert/processor/conversion/aoc/ability/stop.py index bd3119db48..d251cd8b5a 100644 --- a/openage/convert/processor/conversion/aoc/ability/stop.py +++ b/openage/convert/processor/conversion/aoc/ability/stop.py @@ -20,9 +20,7 @@ def stop_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Stop 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 diff --git a/openage/convert/processor/conversion/aoc/ability/storage.py b/openage/convert/processor/conversion/aoc/ability/storage.py index 185b0517ac..8cc9cdd956 100644 --- a/openage/convert/processor/conversion/aoc/ability/storage.py +++ b/openage/convert/processor/conversion/aoc/ability/storage.py @@ -22,9 +22,7 @@ def storage_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Storage 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/terrain_requirement.py b/openage/convert/processor/conversion/aoc/ability/terrain_requirement.py index 4797f6c38d..33f44356ed 100644 --- a/openage/convert/processor/conversion/aoc/ability/terrain_requirement.py +++ b/openage/convert/processor/conversion/aoc/ability/terrain_requirement.py @@ -20,9 +20,7 @@ def terrain_requirement_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the TerrainRequirement to a line. :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :returns: The forward references for the abilities. - :rtype: ...dataformat.forward_ref.ForwardRef """ current_unit = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/trade.py b/openage/convert/processor/conversion/aoc/ability/trade.py index d6bd12ebd5..74fda97861 100644 --- a/openage/convert/processor/conversion/aoc/ability/trade.py +++ b/openage/convert/processor/conversion/aoc/ability/trade.py @@ -20,9 +20,7 @@ def trade_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Trade 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/trade_post.py b/openage/convert/processor/conversion/aoc/ability/trade_post.py index 418120e4e6..865a170d3a 100644 --- a/openage/convert/processor/conversion/aoc/ability/trade_post.py +++ b/openage/convert/processor/conversion/aoc/ability/trade_post.py @@ -20,9 +20,7 @@ def trade_post_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the TradePost 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 diff --git a/openage/convert/processor/conversion/aoc/ability/transfer_storage.py b/openage/convert/processor/conversion/aoc/ability/transfer_storage.py index 2aefa14bc4..e8ad078508 100644 --- a/openage/convert/processor/conversion/aoc/ability/transfer_storage.py +++ b/openage/convert/processor/conversion/aoc/ability/transfer_storage.py @@ -20,9 +20,7 @@ def transfer_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the TransferStorage 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, None """ current_unit_id = line.get_head_unit_id() dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/ability/turn.py b/openage/convert/processor/conversion/aoc/ability/turn.py index f80b615e58..ae0b2a4cf2 100644 --- a/openage/convert/processor/conversion/aoc/ability/turn.py +++ b/openage/convert/processor/conversion/aoc/ability/turn.py @@ -25,9 +25,7 @@ def turn_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Turn 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/use_contingent.py b/openage/convert/processor/conversion/aoc/ability/use_contingent.py index 4a5716b3e2..18dfeee9e5 100644 --- a/openage/convert/processor/conversion/aoc/ability/use_contingent.py +++ b/openage/convert/processor/conversion/aoc/ability/use_contingent.py @@ -20,9 +20,7 @@ def use_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the UseContingent 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 = line.get_head_unit() current_unit_id = line.get_head_unit_id() diff --git a/openage/convert/processor/conversion/aoc/ability/util.py b/openage/convert/processor/conversion/aoc/ability/util.py index 75c8c515b9..5a11194da2 100644 --- a/openage/convert/processor/conversion/aoc/ability/util.py +++ b/openage/convert/processor/conversion/aoc/ability/util.py @@ -31,15 +31,10 @@ def create_animation( Generates an animation for an ability. :param line: ConverterObjectGroup that the animation object is added to. - :type line: ConverterObjectGroup :param animation_id: ID of the animation in the dataset. - :type animation_id: int :param ability_ref: Reference of the object the animation is nested in. - :type ability_ref: str :param obj_name_prefix: Name prefix for the animation object. - :type obj_name_prefix: str :param filename_prefix: Prefix for the animation PNG and sprite files. - :type filename_prefix: str """ dataset = line.data head_unit_id = line.get_head_unit_id() @@ -89,19 +84,12 @@ def create_civ_animation( Generates an animation as a patch for a civ. :param line: ConverterObjectGroup that the animation object is added to. - :type line: ConverterObjectGroup :param civ_group: ConverterObjectGroup that patches the animation object into the ability. - :type civ_group: ConverterObjectGroup :param animation_id: ID of the animation in the dataset. - :type animation_id: int :param location_ref: Reference of the object the resulting object is nested in. - :type location_ref: str :param obj_name_prefix: Name prefix for the object. - :type obj_name_prefix: str :param filename_prefix: Prefix for the animation PNG and sprite files. - :type filename_prefix: str - :param exists: Tells the method if the animation object has already been created. - :type exists: bool + :param exists: Set to true if the animation object has already been created before. """ dataset = civ_group.data head_unit_id = line.get_head_unit_id() @@ -197,15 +185,10 @@ def create_sound( Generates a sound for an ability. :param line: ConverterObjectGroup that the animation object is added to. - :type line: ConverterObjectGroup :param sound_id: ID of the sound in the dataset. - :type sound_id: int :param location_ref: Reference of the object the sound is nested in. - :type location_ref: str :param obj_name_prefix: Name prefix for the sound object. - :type obj_name_prefix: str :param filename_prefix: Prefix for the animation PNG and sprite files. - :type filename_prefix: str """ dataset = line.data @@ -261,13 +244,9 @@ def create_language_strings( Generates a language string for an ability. :param line: ConverterObjectGroup that the animation object is added to. - :type line: ConverterObjectGroup :param string_id: ID of the string in the dataset. - :type string_id: int :param location_ref: Reference of the object the string is nested in. - :type location_ref: str :param obj_name_prefix: Name prefix for the string object. - :type obj_name_prefix: str """ dataset = line.data string_resources = dataset.strings.get_tables() diff --git a/openage/convert/processor/conversion/aoc/ability/visibility.py b/openage/convert/processor/conversion/aoc/ability/visibility.py index c13a62134f..867a68eecd 100644 --- a/openage/convert/processor/conversion/aoc/ability/visibility.py +++ b/openage/convert/processor/conversion/aoc/ability/visibility.py @@ -21,9 +21,7 @@ def visibility_ability(line: GenieGameEntityGroup) -> ForwardRef: Adds the Visibility 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 diff --git a/openage/convert/processor/conversion/aoc/effect/attack.py b/openage/convert/processor/conversion/aoc/effect/attack.py index a918429a93..96f829c47d 100644 --- a/openage/convert/processor/conversion/aoc/effect/attack.py +++ b/openage/convert/processor/conversion/aoc/effect/attack.py @@ -23,11 +23,8 @@ def get_attack_effects( Creates effects that are used for attacking (unit command: 7) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param location_ref: Reference to API object the effects are added to. - :type location_ref: str :returns: The forward references for the effects. - :rtype: list """ dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/effect/construct.py b/openage/convert/processor/conversion/aoc/effect/construct.py index 1a41c9d096..41a301df01 100644 --- a/openage/convert/processor/conversion/aoc/effect/construct.py +++ b/openage/convert/processor/conversion/aoc/effect/construct.py @@ -22,11 +22,8 @@ def get_construct_effects( Creates effects that are used for construction (unit command: 101) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param location_ref: Reference to API object the effects are added to. - :type location_ref: str :returns: The forward references for the effects. - :rtype: list """ dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/effect/convert.py b/openage/convert/processor/conversion/aoc/effect/convert.py index dcea8dae05..2c53c01a63 100644 --- a/openage/convert/processor/conversion/aoc/effect/convert.py +++ b/openage/convert/processor/conversion/aoc/effect/convert.py @@ -21,11 +21,8 @@ def get_convert_effects( Creates effects that are used for conversion (unit command: 104) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param location_ref: Reference to API object the effects are added to. - :type location_ref: str :returns: The forward references for the effects. - :rtype: list """ current_unit = line.get_head_unit() dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/effect/heal.py b/openage/convert/processor/conversion/aoc/effect/heal.py index d12149f784..78fbf0568e 100644 --- a/openage/convert/processor/conversion/aoc/effect/heal.py +++ b/openage/convert/processor/conversion/aoc/effect/heal.py @@ -21,11 +21,8 @@ def get_heal_effects( Creates effects that are used for healing (unit command: 105) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param location_ref: Reference to API object the effects are added to. - :type location_ref: str :returns: The forward references for the effects. - :rtype: list """ current_unit = line.get_head_unit() dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/effect/repair.py b/openage/convert/processor/conversion/aoc/effect/repair.py index a759f6fbe7..29056ca224 100644 --- a/openage/convert/processor/conversion/aoc/effect/repair.py +++ b/openage/convert/processor/conversion/aoc/effect/repair.py @@ -23,11 +23,8 @@ def get_repair_effects( Creates effects that are used for repairing (unit command: 106) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param location_ref: Reference to API object the effects are added to. - :type location_ref: str :returns: The forward references for the effects. - :rtype: list """ dataset = line.data api_objects = dataset.nyan_api_objects diff --git a/openage/convert/processor/conversion/aoc/resistance/attack.py b/openage/convert/processor/conversion/aoc/resistance/attack.py index 6aa10bce85..5dfcf4c4a4 100644 --- a/openage/convert/processor/conversion/aoc/resistance/attack.py +++ b/openage/convert/processor/conversion/aoc/resistance/attack.py @@ -22,11 +22,8 @@ def get_attack_resistances( Creates resistances that are used for attacking (unit command: 7) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str :returns: The forward references for the effects. - :rtype: list """ current_unit = line.get_head_unit() dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/resistance/construct.py b/openage/convert/processor/conversion/aoc/resistance/construct.py index b05843f78b..180b651d17 100644 --- a/openage/convert/processor/conversion/aoc/resistance/construct.py +++ b/openage/convert/processor/conversion/aoc/resistance/construct.py @@ -22,11 +22,8 @@ def get_construct_resistances( Creates resistances that are used for constructing (unit command: 101) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str :returns: The forward references for the effects. - :rtype: list """ current_unit_id = line.get_head_unit_id() dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/resistance/convert.py b/openage/convert/processor/conversion/aoc/resistance/convert.py index 4c87a72430..c4c01a21c5 100644 --- a/openage/convert/processor/conversion/aoc/resistance/convert.py +++ b/openage/convert/processor/conversion/aoc/resistance/convert.py @@ -23,11 +23,8 @@ def get_convert_resistances( Creates resistances that are used for conversion (unit command: 104) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str :returns: The forward references for the effects. - :rtype: list """ dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/resistance/heal.py b/openage/convert/processor/conversion/aoc/resistance/heal.py index 02f8feab5e..41020cd230 100644 --- a/openage/convert/processor/conversion/aoc/resistance/heal.py +++ b/openage/convert/processor/conversion/aoc/resistance/heal.py @@ -21,11 +21,8 @@ def get_heal_resistances( Creates resistances that are used for healing (unit command: 105) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str :returns: The forward references for the effects. - :rtype: list """ dataset = line.data diff --git a/openage/convert/processor/conversion/aoc/resistance/repair.py b/openage/convert/processor/conversion/aoc/resistance/repair.py index 1bee078cff..997508791e 100644 --- a/openage/convert/processor/conversion/aoc/resistance/repair.py +++ b/openage/convert/processor/conversion/aoc/resistance/repair.py @@ -22,11 +22,8 @@ def get_repair_resistances( Creates resistances that are used for repairing (unit command: 106) :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str :returns: The forward references for the effects. - :rtype: list """ current_unit_id = line.get_head_unit_id() dataset = line.data From 3487e2f4d8f26fe66c555eff23ec6cff26836a97 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 29 May 2025 11:16:30 +0200 Subject: [PATCH 112/163] convert: Refactor AoCAuxiliarySubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../conversion/aoc/auxiliary/CMakeLists.txt | 6 + .../conversion/aoc/auxiliary/__init__.py | 6 + .../aoc/auxiliary/creatable_game_entity.py | 389 +++++++++ .../aoc/auxiliary/researchable_tech.py | 182 +++++ .../conversion/aoc/auxiliary/util.py | 218 +++++ .../conversion/aoc/auxiliary_subprocessor.py | 772 +----------------- 7 files changed, 811 insertions(+), 763 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/auxiliary/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/auxiliary/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/auxiliary/creatable_game_entity.py create mode 100644 openage/convert/processor/conversion/aoc/auxiliary/researchable_tech.py create mode 100644 openage/convert/processor/conversion/aoc/auxiliary/util.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index c65ce6ef57..d26b6749b7 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -18,5 +18,6 @@ add_py_modules( ) add_subdirectory(ability) +add_subdirectory(auxiliary) add_subdirectory(effect) add_subdirectory(resistance) diff --git a/openage/convert/processor/conversion/aoc/auxiliary/CMakeLists.txt b/openage/convert/processor/conversion/aoc/auxiliary/CMakeLists.txt new file mode 100644 index 0000000000..a368c22993 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/auxiliary/CMakeLists.txt @@ -0,0 +1,6 @@ +add_py_modules( + __init__.py + creatable_game_entity.py + researchable_tech.py + util.py +) diff --git a/openage/convert/processor/conversion/aoc/auxiliary/__init__.py b/openage/convert/processor/conversion/aoc/auxiliary/__init__.py new file mode 100644 index 0000000000..d2825e251d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/auxiliary/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Derives creatables or researchables objects from unit lines, techs +or other objects. +""" diff --git a/openage/convert/processor/conversion/aoc/auxiliary/creatable_game_entity.py b/openage/convert/processor/conversion/aoc/auxiliary/creatable_game_entity.py new file mode 100644 index 0000000000..bd20da7a5d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/auxiliary/creatable_game_entity.py @@ -0,0 +1,389 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for creatables (units). +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ + GenieBuildingLineGroup, GenieUnitLineGroup +from .....entity_object.conversion.combined_sound import CombinedSound +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import get_condition + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_creatable_game_entity(line: GenieGameEntityGroup) -> None: + """ + Creates the CreatableGameEntity object for a unit/building line. + + :param line: Unit/Building line. + """ + if isinstance(line, GenieVillagerGroup): + current_unit = line.variants[0].line[0] + + else: + current_unit = line.line[0] + + current_unit_id = line.get_head_unit_id() + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + obj_ref = f"{game_entity_name}.CreatableGameEntity" + obj_name = f"{game_entity_name}Creatable" + creatable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) + creatable_raw_api_object.add_raw_parent("engine.util.create.CreatableGameEntity") + + # Get train location of line + train_location_id = line.get_train_location_id() + if isinstance(line, GenieBuildingLineGroup): + train_location = dataset.unit_lines[train_location_id] + train_location_name = name_lookup_dict[train_location_id][0] + + else: + train_location = dataset.building_lines[train_location_id] + train_location_name = name_lookup_dict[train_location_id][0] + + # Location of the object depends on whether it'a a unique unit or a normal unit + if line.is_unique(): + # Add object to the Civ object + enabling_research_id = line.get_enabling_research_id() + enabling_research = dataset.genie_techs[enabling_research_id] + enabling_civ_id = enabling_research["civilization_id"].value + + civ = dataset.civ_groups[enabling_civ_id] + civ_name = civ_lookup_dict[enabling_civ_id][0] + + creatable_location = ForwardRef(civ, civ_name) + + else: + # Add object to the train location's Create ability + creatable_location = ForwardRef(train_location, + f"{train_location_name}.Create") + + creatable_raw_api_object.set_location(creatable_location) + + # Game Entity + game_entity_forward_ref = ForwardRef(line, game_entity_name) + creatable_raw_api_object.add_raw_member("game_entity", + game_entity_forward_ref, + "engine.util.create.CreatableGameEntity") + + # TODO: Variants + variants_set = [] + + creatable_raw_api_object.add_raw_member("variants", variants_set, + "engine.util.create.CreatableGameEntity") + + # Cost (construction) + cost_name = f"{game_entity_name}.CreatableGameEntity.{game_entity_name}Cost" + cost_raw_api_object = RawAPIObject(cost_name, + f"{game_entity_name}Cost", + dataset.nyan_api_objects) + cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") + creatable_forward_ref = ForwardRef(line, obj_ref) + cost_raw_api_object.set_location(creatable_forward_ref) + + payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] + cost_raw_api_object.add_raw_member("payment_mode", + payment_mode, + "engine.util.cost.Cost") + + if line.is_repairable(): + # Cost (repair) for buildings + cost_repair_name = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}RepairCost") + cost_repair_raw_api_object = RawAPIObject(cost_repair_name, + f"{game_entity_name}RepairCost", + dataset.nyan_api_objects) + cost_repair_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") + creatable_forward_ref = ForwardRef(line, obj_ref) + cost_repair_raw_api_object.set_location(creatable_forward_ref) + + payment_repair_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Adaptive"] + cost_repair_raw_api_object.add_raw_member("payment_mode", + payment_repair_mode, + "engine.util.cost.Cost") + line.add_raw_api_object(cost_repair_raw_api_object) + + cost_amounts = [] + cost_repair_amounts = [] + for resource_amount in current_unit["resource_cost"].value: + resource_id = resource_amount["type_id"].value + + resource = None + resource_name = "" + if resource_id == -1: + # Not a valid resource + continue + + if resource_id == 0: + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + resource_name = "Food" + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() + resource_name = "Wood" + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( + ) + resource_name = "Stone" + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() + resource_name = "Gold" + + else: + # Other resource ids are handled differently + continue + + # Skip resources that are only expected to be there + if not resource_amount["enabled"].value: + continue + + amount = resource_amount["amount"].value + + cost_amount_name = f"{cost_name}.{resource_name}Amount" + cost_amount = RawAPIObject(cost_amount_name, + f"{resource_name}Amount", + dataset.nyan_api_objects) + cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") + cost_forward_ref = ForwardRef(line, cost_name) + cost_amount.set_location(cost_forward_ref) + + cost_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + cost_amount.add_raw_member("amount", + amount, + "engine.util.resource.ResourceAmount") + + cost_amount_forward_ref = ForwardRef(line, cost_amount_name) + cost_amounts.append(cost_amount_forward_ref) + line.add_raw_api_object(cost_amount) + + if line.is_repairable(): + # Cost for repairing = half of the construction cost + cost_amount_name = f"{cost_repair_name}.{resource_name}Amount" + cost_amount = RawAPIObject(cost_amount_name, + f"{resource_name}Amount", + dataset.nyan_api_objects) + cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") + cost_forward_ref = ForwardRef(line, cost_repair_name) + cost_amount.set_location(cost_forward_ref) + + cost_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + cost_amount.add_raw_member("amount", + amount / 2, + "engine.util.resource.ResourceAmount") + + cost_amount_forward_ref = ForwardRef(line, cost_amount_name) + cost_repair_amounts.append(cost_amount_forward_ref) + line.add_raw_api_object(cost_amount) + + cost_raw_api_object.add_raw_member("amount", + cost_amounts, + "engine.util.cost.type.ResourceCost") + + if line.is_repairable(): + cost_repair_raw_api_object.add_raw_member("amount", + cost_repair_amounts, + "engine.util.cost.type.ResourceCost") + + cost_forward_ref = ForwardRef(line, cost_name) + creatable_raw_api_object.add_raw_member("cost", + cost_forward_ref, + "engine.util.create.CreatableGameEntity") + # Creation time + if isinstance(line, GenieUnitLineGroup): + creation_time = current_unit["creation_time"].value + + else: + # Buildings are created immediately + creation_time = 0 + + creatable_raw_api_object.add_raw_member("creation_time", + creation_time, + "engine.util.create.CreatableGameEntity") + + # Creation sound + creation_sound_id = current_unit["train_sound_id"].value + + # Create sound object + obj_name = f"{game_entity_name}.CreatableGameEntity.Sound" + sound_raw_api_object = RawAPIObject(obj_name, "CreationSound", + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(line, obj_ref) + sound_raw_api_object.set_location(sound_location) + + # Search for the sound if it exists + creation_sounds = [] + if creation_sound_id > -1: + # Creation sound should be civ agnostic + genie_sound = dataset.genie_sounds[creation_sound_id] + file_id = genie_sound.get_sounds(civ_id=-1)[0] + + if file_id in dataset.combined_sounds: + creation_sound = dataset.combined_sounds[file_id] + creation_sound.add_reference(sound_raw_api_object) + + else: + creation_sound = CombinedSound(creation_sound_id, + file_id, + f"creation_sound_{creation_sound_id}", + dataset) + dataset.combined_sounds.update({file_id: creation_sound}) + creation_sound.add_reference(sound_raw_api_object) + + creation_sounds.append(creation_sound) + + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + creation_sounds, + "engine.util.sound.Sound") + + sound_forward_ref = ForwardRef(line, obj_name) + creatable_raw_api_object.add_raw_member("creation_sounds", + [sound_forward_ref], + "engine.util.create.CreatableGameEntity") + + line.add_raw_api_object(sound_raw_api_object) + + # Condition + unlock_conditions = [] + enabling_research_id = line.get_enabling_research_id() + if enabling_research_id > -1: + unlock_conditions.extend(get_condition(line, + obj_ref, + enabling_research_id)) + + creatable_raw_api_object.add_raw_member("condition", + unlock_conditions, + "engine.util.create.CreatableGameEntity") + + # Placement modes + placement_modes = [] + if isinstance(line, GenieBuildingLineGroup): + # Buildings are placed on the map + # Place mode + obj_name = f"{game_entity_name}.CreatableGameEntity.Place" + place_raw_api_object = RawAPIObject(obj_name, + "Place", + dataset.nyan_api_objects) + place_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Place") + place_location = ForwardRef(line, + f"{game_entity_name}.CreatableGameEntity") + place_raw_api_object.set_location(place_location) + + # Tile snap distance (uses 1.0 for grid placement) + place_raw_api_object.add_raw_member("tile_snap_distance", + 1.0, + "engine.util.placement_mode.type.Place") + # Clearance size + clearance_size_x = current_unit["clearance_size_x"].value + clearance_size_y = current_unit["clearance_size_y"].value + place_raw_api_object.add_raw_member("clearance_size_x", + clearance_size_x, + "engine.util.placement_mode.type.Place") + place_raw_api_object.add_raw_member("clearance_size_y", + clearance_size_y, + "engine.util.placement_mode.type.Place") + + # Allow rotation + place_raw_api_object.add_raw_member("allow_rotation", + True, + "engine.util.placement_mode.type.Place") + + # Max elevation difference + elevation_mode = current_unit["elevation_mode"].value + if elevation_mode == 2: + max_elevation_difference = 0 + + elif elevation_mode == 3: + max_elevation_difference = 1 + + else: + max_elevation_difference = MemberSpecialValue.NYAN_INF + + place_raw_api_object.add_raw_member("max_elevation_difference", + max_elevation_difference, + "engine.util.placement_mode.type.Place") + + line.add_raw_api_object(place_raw_api_object) + + place_forward_ref = ForwardRef(line, obj_name) + placement_modes.append(place_forward_ref) + + if line.get_class_id() == 39: + # Gates + obj_name = f"{game_entity_name}.CreatableGameEntity.Replace" + replace_raw_api_object = RawAPIObject(obj_name, + "Replace", + dataset.nyan_api_objects) + replace_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Replace") + replace_location = ForwardRef(line, + f"{game_entity_name}.CreatableGameEntity") + replace_raw_api_object.set_location(replace_location) + + # Game entities (only stone wall) + wall_line_id = 117 + wall_line = dataset.building_lines[wall_line_id] + wall_name = name_lookup_dict[117][0] + game_entities = [ForwardRef(wall_line, wall_name)] + replace_raw_api_object.add_raw_member("game_entities", + game_entities, + "engine.util.placement_mode.type.Replace") + + line.add_raw_api_object(replace_raw_api_object) + + replace_forward_ref = ForwardRef(line, obj_name) + placement_modes.append(replace_forward_ref) + + else: + placement_modes.append( + dataset.nyan_api_objects["engine.util.placement_mode.type.Eject"]) + + # OwnStorage mode + obj_name = f"{game_entity_name}.CreatableGameEntity.OwnStorage" + own_storage_raw_api_object = RawAPIObject(obj_name, "OwnStorage", + dataset.nyan_api_objects) + own_storage_raw_api_object.add_raw_parent("engine.util.placement_mode.type.OwnStorage") + own_storage_location = ForwardRef(line, + f"{game_entity_name}.CreatableGameEntity") + own_storage_raw_api_object.set_location(own_storage_location) + + # Container + container_forward_ref = ForwardRef(train_location, + (f"{train_location_name}.Storage." + f"{train_location_name}Container")) + own_storage_raw_api_object.add_raw_member("container", + container_forward_ref, + "engine.util.placement_mode.type.OwnStorage") + + line.add_raw_api_object(own_storage_raw_api_object) + + own_storage_forward_ref = ForwardRef(line, obj_name) + placement_modes.append(own_storage_forward_ref) + + creatable_raw_api_object.add_raw_member("placement_modes", + placement_modes, + "engine.util.create.CreatableGameEntity") + + line.add_raw_api_object(creatable_raw_api_object) + line.add_raw_api_object(cost_raw_api_object) diff --git a/openage/convert/processor/conversion/aoc/auxiliary/researchable_tech.py b/openage/convert/processor/conversion/aoc/auxiliary/researchable_tech.py new file mode 100644 index 0000000000..56400266d1 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/auxiliary/researchable_tech.py @@ -0,0 +1,182 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for researchables (techs). +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .util import get_condition + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup + + +def get_researchable_tech(tech_group: GenieTechEffectBundleGroup) -> None: + """ + Creates the ResearchableTech object for a Tech. + + :param tech_group: Tech group that is a technology. + """ + dataset = tech_group.data + research_location_id = tech_group.get_research_location_id() + research_location = dataset.building_lines[research_location_id] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + research_location_name = name_lookup_dict[research_location_id][0] + tech_name = tech_lookup_dict[tech_group.get_id()][0] + + obj_ref = f"{tech_name}.ResearchableTech" + obj_name = f"{tech_name}Researchable" + researchable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) + researchable_raw_api_object.add_raw_parent("engine.util.research.ResearchableTech") + + # Location of the object depends on whether it'a a unique tech or a normal tech + if tech_group.is_unique(): + # Add object to the Civ object + civ_id = tech_group.get_civilization() + civ = dataset.civ_groups[civ_id] + civ_name = civ_lookup_dict[civ_id][0] + + researchable_location = ForwardRef(civ, civ_name) + + else: + # Add object to the research location's Research ability + researchable_location = ForwardRef(research_location, + f"{research_location_name}.Research") + + researchable_raw_api_object.set_location(researchable_location) + + # Tech + tech_forward_ref = ForwardRef(tech_group, tech_name) + researchable_raw_api_object.add_raw_member("tech", + tech_forward_ref, + "engine.util.research.ResearchableTech") + + # Cost + cost_ref = f"{tech_name}.ResearchableTech.{tech_name}Cost" + cost_raw_api_object = RawAPIObject(cost_ref, + f"{tech_name}Cost", + dataset.nyan_api_objects) + cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") + tech_forward_ref = ForwardRef(tech_group, obj_ref) + cost_raw_api_object.set_location(tech_forward_ref) + + payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] + cost_raw_api_object.add_raw_member("payment_mode", + payment_mode, + "engine.util.cost.Cost") + + cost_amounts = [] + for resource_amount in tech_group.tech["research_resource_costs"].value: + resource_id = resource_amount["type_id"].value + resource = None + resource_name = "" + if resource_id == -1: + # Not a valid resource + continue + + if resource_id == 0: + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + resource_name = "Food" + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() + resource_name = "Wood" + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( + ) + resource_name = "Stone" + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() + resource_name = "Gold" + + else: + # Other resource ids are handled differently + continue + + # Skip resources that are only expected to be there + if not resource_amount["enabled"].value: + continue + + amount = resource_amount["amount"].value + + cost_amount_ref = f"{cost_ref}.{resource_name}Amount" + cost_amount = RawAPIObject(cost_amount_ref, + f"{resource_name}Amount", + dataset.nyan_api_objects) + cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") + cost_forward_ref = ForwardRef(tech_group, cost_ref) + cost_amount.set_location(cost_forward_ref) + + cost_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + cost_amount.add_raw_member("amount", + amount, + "engine.util.resource.ResourceAmount") + + cost_amount_forward_ref = ForwardRef(tech_group, cost_amount_ref) + cost_amounts.append(cost_amount_forward_ref) + tech_group.add_raw_api_object(cost_amount) + + cost_raw_api_object.add_raw_member("amount", + cost_amounts, + "engine.util.cost.type.ResourceCost") + + cost_forward_ref = ForwardRef(tech_group, cost_ref) + researchable_raw_api_object.add_raw_member("cost", + cost_forward_ref, + "engine.util.research.ResearchableTech") + + research_time = tech_group.tech["research_time"].value + researchable_raw_api_object.add_raw_member("research_time", + research_time, + "engine.util.research.ResearchableTech") + + # Create sound object + sound_ref = f"{tech_name}.ResearchableTech.Sound" + sound_raw_api_object = RawAPIObject(sound_ref, "ResearchSound", + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(tech_group, + f"{tech_name}.ResearchableTech") + sound_raw_api_object.set_location(sound_location) + + # AoE doesn't support sounds here, so this is empty + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + [], + "engine.util.sound.Sound") + + sound_forward_ref = ForwardRef(tech_group, sound_ref) + researchable_raw_api_object.add_raw_member("research_sounds", + [sound_forward_ref], + "engine.util.research.ResearchableTech") + + tech_group.add_raw_api_object(sound_raw_api_object) + + # Condition + unlock_conditions = [] + if tech_group.get_id() > -1: + unlock_conditions.extend(get_condition(tech_group, + obj_ref, + tech_group.get_id(), + top_level=True)) + + researchable_raw_api_object.add_raw_member("condition", + unlock_conditions, + "engine.util.research.ResearchableTech") + + tech_group.add_raw_api_object(researchable_raw_api_object) + tech_group.add_raw_api_object(cost_raw_api_object) diff --git a/openage/convert/processor/conversion/aoc/auxiliary/util.py b/openage/convert/processor/conversion/aoc/auxiliary/util.py new file mode 100644 index 0000000000..da424c7874 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/auxiliary/util.py @@ -0,0 +1,218 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Helper functions for AoC creatables and researchables. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + + +def get_condition( + converter_obj_group: ConverterObjectGroup, + obj_ref: str, + tech_id: int, + top_level: bool = False +) -> list[ForwardRef]: + """ + Creates the condition for a creatable or researchable from tech + by recursively searching the required techs. + + :param converter_object: ConverterObjectGroup that the condition objects should be nested in. + :param obj_ref: Reference of converter_object inside the modpack. + :param tech_id: tech ID of a tech wth a conditional unlock. + :param top_level: True if the condition has subconditions, False otherwise. + """ + dataset = converter_obj_group.data + tech = dataset.genie_techs[tech_id] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + if not top_level and\ + (tech_id in dataset.initiated_techs.keys() or + (tech_id in dataset.tech_groups.keys() and + dataset.tech_groups[tech_id].is_researchable())): + # The tech condition is a building or a researchable tech + # and thus a literal. + if tech_id in dataset.initiated_techs.keys(): + initiated_tech = dataset.initiated_techs[tech_id] + building_id = initiated_tech.get_building_id() + building_name = name_lookup_dict[building_id][0] + literal_name = f"{building_name}Built" + literal_parent = "engine.util.logic.literal.type.GameEntityProgress" + + elif dataset.tech_groups[tech_id].is_researchable(): + tech_name = tech_lookup_dict[tech_id][0] + literal_name = f"{tech_name}Researched" + literal_parent = "engine.util.logic.literal.type.TechResearched" + + else: + raise ValueError("Required tech id {tech_id} is neither intiated nor researchable") + + literal_ref = f"{obj_ref}.{literal_name}" + literal_raw_api_object = RawAPIObject(literal_ref, + literal_name, + dataset.nyan_api_objects) + literal_raw_api_object.add_raw_parent(literal_parent) + literal_location = ForwardRef(converter_obj_group, obj_ref) + literal_raw_api_object.set_location(literal_location) + + if tech_id in dataset.initiated_techs.keys(): + building_line = dataset.unit_ref[building_id] + building_forward_ref = ForwardRef(building_line, building_name) + + # Building + literal_raw_api_object.add_raw_member("game_entity", + building_forward_ref, + literal_parent) + + # Progress + # ======================================================================= + progress_ref = f"{literal_ref}.ProgressStatus" + progress_raw_api_object = RawAPIObject(progress_ref, + "ProgressStatus", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress_status.ProgressStatus") + progress_location = ForwardRef(converter_obj_group, literal_ref) + progress_raw_api_object.set_location(progress_location) + + # Type + progress_type = dataset.nyan_api_objects["engine.util.progress_type.type.Construct"] + progress_raw_api_object.add_raw_member("progress_type", + progress_type, + "engine.util.progress_status.ProgressStatus") + + # Progress (building must be 100% constructed) + progress_raw_api_object.add_raw_member("progress", + 100, + "engine.util.progress_status.ProgressStatus") + + converter_obj_group.add_raw_api_object(progress_raw_api_object) + # ======================================================================= + progress_forward_ref = ForwardRef(converter_obj_group, progress_ref) + literal_raw_api_object.add_raw_member("progress_status", + progress_forward_ref, + literal_parent) + + elif dataset.tech_groups[tech_id].is_researchable(): + tech_group = dataset.tech_groups[tech_id] + tech_forward_ref = ForwardRef(tech_group, tech_name) + literal_raw_api_object.add_raw_member("tech", + tech_forward_ref, + literal_parent) + + # LiteralScope + # ========================================================================== + scope_ref = f"{literal_ref}.LiteralScope" + scope_raw_api_object = RawAPIObject(scope_ref, + "LiteralScope", + dataset.nyan_api_objects) + scope_raw_api_object.add_raw_parent("engine.util.logic.literal_scope.type.Any") + scope_location = ForwardRef(converter_obj_group, literal_ref) + scope_raw_api_object.set_location(scope_location) + + scope_diplomatic_stances = [ + dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"] + ] + scope_raw_api_object.add_raw_member("stances", + scope_diplomatic_stances, + "engine.util.logic.literal_scope.LiteralScope") + + converter_obj_group.add_raw_api_object(scope_raw_api_object) + # ========================================================================== + scope_forward_ref = ForwardRef(converter_obj_group, scope_ref) + literal_raw_api_object.add_raw_member("scope", + scope_forward_ref, + "engine.util.logic.literal.Literal") + + literal_raw_api_object.add_raw_member("only_once", + True, + "engine.util.logic.LogicElement") + + converter_obj_group.add_raw_api_object(literal_raw_api_object) + literal_forward_ref = ForwardRef(converter_obj_group, literal_ref) + + return [literal_forward_ref] + + else: + # The tech condition has other requirements that need to be resolved + + # Find required techs for the current tech + assoc_tech_id_members = [] + assoc_tech_id_members.extend(tech["required_techs"].value) + required_tech_count = tech["required_tech_count"].value + + # Remove tech ids that are invalid or those we don't use + relevant_ids = [] + for tech_id_member in assoc_tech_id_members: + required_tech_id = tech_id_member.value + if required_tech_id == -1: + continue + + if required_tech_id == 104: + # Skip Dark Age tech + required_tech_count -= 1 + continue + + if required_tech_id in dataset.civ_boni.keys(): + continue + + relevant_ids.append(required_tech_id) + + if len(relevant_ids) == 0: + return [] + + if len(relevant_ids) == 1: + # If there's only one required tech we don't need a gate + # we can just return the logic element of the only required tech + required_tech_id = relevant_ids[0] + return get_condition(converter_obj_group, + obj_ref, + required_tech_id) + + gate_ref = f"{obj_ref}.UnlockCondition" + gate_raw_api_object = RawAPIObject(gate_ref, + "UnlockCondition", + dataset.nyan_api_objects) + + if required_tech_count == len(relevant_ids): + gate_raw_api_object.add_raw_parent("engine.util.logic.gate.type.AND") + gate_location = ForwardRef(converter_obj_group, obj_ref) + + else: + gate_raw_api_object.add_raw_parent("engine.util.logic.gate.type.SUBSETMIN") + gate_location = ForwardRef(converter_obj_group, obj_ref) + + gate_raw_api_object.add_raw_member("size", + required_tech_count, + "engine.util.logic.gate.type.SUBSETMIN") + + gate_raw_api_object.set_location(gate_location) + + # Once unlocked, a creatable/researchable is unlocked forever + gate_raw_api_object.add_raw_member("only_once", + True, + "engine.util.logic.LogicElement") + + # Get requirements from subtech recursively + inputs = [] + for required_tech_id in relevant_ids: + required = get_condition(converter_obj_group, + gate_ref, + required_tech_id) + inputs.extend(required) + + gate_raw_api_object.add_raw_member("inputs", + inputs, + "engine.util.logic.gate.LogicGate") + + converter_obj_group.add_raw_api_object(gate_raw_api_object) + gate_forward_ref = ForwardRef(converter_obj_group, gate_ref) + return [gate_forward_ref] diff --git a/openage/convert/processor/conversion/aoc/auxiliary_subprocessor.py b/openage/convert/processor/conversion/aoc/auxiliary_subprocessor.py index 618666d32f..aee8ad0e7c 100644 --- a/openage/convert/processor/conversion/aoc/auxiliary_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/auxiliary_subprocessor.py @@ -1,772 +1,18 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=line-too-long,too-many-locals,too-many-branches,too-many-statements,no-else-return +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ -Derives complex auxiliary objects from unit lines, techs -or other objects. +Derives utility objects from unit lines, techs, or other objects. """ -from __future__ import annotations -import typing - - -from .....nyan.nyan_structs import MemberSpecialValue -from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ - GenieBuildingLineGroup, GenieUnitLineGroup -from ....entity_object.conversion.combined_sound import CombinedSound -from ....entity_object.conversion.converter_object import ConverterObjectGroup, RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup - from openage.convert.entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .auxiliary.creatable_game_entity import get_creatable_game_entity +from .auxiliary.researchable_tech import get_researchable_tech +from .auxiliary.util import get_condition class AoCAuxiliarySubprocessor: """ - Creates complexer auxiliary raw API objects for abilities in AoC. + Creates utility raw API objects for Create and Research abilities. """ - @staticmethod - def get_creatable_game_entity(line: GenieGameEntityGroup) -> None: - """ - Creates the CreatableGameEntity object for a unit/building line. - - :param line: Unit/Building line. - :type line: ...dataformat.converter_object.ConverterObjectGroup - """ - if isinstance(line, GenieVillagerGroup): - current_unit = line.variants[0].line[0] - - else: - current_unit = line.line[0] - - current_unit_id = line.get_head_unit_id() - dataset = line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - obj_ref = f"{game_entity_name}.CreatableGameEntity" - obj_name = f"{game_entity_name}Creatable" - creatable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) - creatable_raw_api_object.add_raw_parent("engine.util.create.CreatableGameEntity") - - # Get train location of line - train_location_id = line.get_train_location_id() - if isinstance(line, GenieBuildingLineGroup): - train_location = dataset.unit_lines[train_location_id] - train_location_name = name_lookup_dict[train_location_id][0] - - else: - train_location = dataset.building_lines[train_location_id] - train_location_name = name_lookup_dict[train_location_id][0] - - # Location of the object depends on whether it'a a unique unit or a normal unit - if line.is_unique(): - # Add object to the Civ object - enabling_research_id = line.get_enabling_research_id() - enabling_research = dataset.genie_techs[enabling_research_id] - enabling_civ_id = enabling_research["civilization_id"].value - - civ = dataset.civ_groups[enabling_civ_id] - civ_name = civ_lookup_dict[enabling_civ_id][0] - - creatable_location = ForwardRef(civ, civ_name) - - else: - # Add object to the train location's Create ability - creatable_location = ForwardRef(train_location, - f"{train_location_name}.Create") - - creatable_raw_api_object.set_location(creatable_location) - - # Game Entity - game_entity_forward_ref = ForwardRef(line, game_entity_name) - creatable_raw_api_object.add_raw_member("game_entity", - game_entity_forward_ref, - "engine.util.create.CreatableGameEntity") - - # TODO: Variants - variants_set = [] - - creatable_raw_api_object.add_raw_member("variants", variants_set, - "engine.util.create.CreatableGameEntity") - - # Cost (construction) - cost_name = f"{game_entity_name}.CreatableGameEntity.{game_entity_name}Cost" - cost_raw_api_object = RawAPIObject(cost_name, - f"{game_entity_name}Cost", - dataset.nyan_api_objects) - cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") - creatable_forward_ref = ForwardRef(line, obj_ref) - cost_raw_api_object.set_location(creatable_forward_ref) - - payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] - cost_raw_api_object.add_raw_member("payment_mode", - payment_mode, - "engine.util.cost.Cost") - - if line.is_repairable(): - # Cost (repair) for buildings - cost_repair_name = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}RepairCost") - cost_repair_raw_api_object = RawAPIObject(cost_repair_name, - f"{game_entity_name}RepairCost", - dataset.nyan_api_objects) - cost_repair_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") - creatable_forward_ref = ForwardRef(line, obj_ref) - cost_repair_raw_api_object.set_location(creatable_forward_ref) - - payment_repair_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Adaptive"] - cost_repair_raw_api_object.add_raw_member("payment_mode", - payment_repair_mode, - "engine.util.cost.Cost") - line.add_raw_api_object(cost_repair_raw_api_object) - - cost_amounts = [] - cost_repair_amounts = [] - for resource_amount in current_unit["resource_cost"].value: - resource_id = resource_amount["type_id"].value - - resource = None - resource_name = "" - if resource_id == -1: - # Not a valid resource - continue - - if resource_id == 0: - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - resource_name = "Food" - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() - resource_name = "Wood" - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( - ) - resource_name = "Stone" - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() - resource_name = "Gold" - - else: - # Other resource ids are handled differently - continue - - # Skip resources that are only expected to be there - if not resource_amount["enabled"].value: - continue - - amount = resource_amount["amount"].value - - cost_amount_name = f"{cost_name}.{resource_name}Amount" - cost_amount = RawAPIObject(cost_amount_name, - f"{resource_name}Amount", - dataset.nyan_api_objects) - cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") - cost_forward_ref = ForwardRef(line, cost_name) - cost_amount.set_location(cost_forward_ref) - - cost_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - cost_amount.add_raw_member("amount", - amount, - "engine.util.resource.ResourceAmount") - - cost_amount_forward_ref = ForwardRef(line, cost_amount_name) - cost_amounts.append(cost_amount_forward_ref) - line.add_raw_api_object(cost_amount) - - if line.is_repairable(): - # Cost for repairing = half of the construction cost - cost_amount_name = f"{cost_repair_name}.{resource_name}Amount" - cost_amount = RawAPIObject(cost_amount_name, - f"{resource_name}Amount", - dataset.nyan_api_objects) - cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") - cost_forward_ref = ForwardRef(line, cost_repair_name) - cost_amount.set_location(cost_forward_ref) - - cost_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - cost_amount.add_raw_member("amount", - amount / 2, - "engine.util.resource.ResourceAmount") - - cost_amount_forward_ref = ForwardRef(line, cost_amount_name) - cost_repair_amounts.append(cost_amount_forward_ref) - line.add_raw_api_object(cost_amount) - - cost_raw_api_object.add_raw_member("amount", - cost_amounts, - "engine.util.cost.type.ResourceCost") - - if line.is_repairable(): - cost_repair_raw_api_object.add_raw_member("amount", - cost_repair_amounts, - "engine.util.cost.type.ResourceCost") - - cost_forward_ref = ForwardRef(line, cost_name) - creatable_raw_api_object.add_raw_member("cost", - cost_forward_ref, - "engine.util.create.CreatableGameEntity") - # Creation time - if isinstance(line, GenieUnitLineGroup): - creation_time = current_unit["creation_time"].value - - else: - # Buildings are created immediately - creation_time = 0 - - creatable_raw_api_object.add_raw_member("creation_time", - creation_time, - "engine.util.create.CreatableGameEntity") - - # Creation sound - creation_sound_id = current_unit["train_sound_id"].value - - # Create sound object - obj_name = f"{game_entity_name}.CreatableGameEntity.Sound" - sound_raw_api_object = RawAPIObject(obj_name, "CreationSound", - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(line, obj_ref) - sound_raw_api_object.set_location(sound_location) - - # Search for the sound if it exists - creation_sounds = [] - if creation_sound_id > -1: - # Creation sound should be civ agnostic - genie_sound = dataset.genie_sounds[creation_sound_id] - file_id = genie_sound.get_sounds(civ_id=-1)[0] - - if file_id in dataset.combined_sounds: - creation_sound = dataset.combined_sounds[file_id] - creation_sound.add_reference(sound_raw_api_object) - - else: - creation_sound = CombinedSound(creation_sound_id, - file_id, - f"creation_sound_{creation_sound_id}", - dataset) - dataset.combined_sounds.update({file_id: creation_sound}) - creation_sound.add_reference(sound_raw_api_object) - - creation_sounds.append(creation_sound) - - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - creation_sounds, - "engine.util.sound.Sound") - - sound_forward_ref = ForwardRef(line, obj_name) - creatable_raw_api_object.add_raw_member("creation_sounds", - [sound_forward_ref], - "engine.util.create.CreatableGameEntity") - - line.add_raw_api_object(sound_raw_api_object) - - # Condition - unlock_conditions = [] - enabling_research_id = line.get_enabling_research_id() - if enabling_research_id > -1: - unlock_conditions.extend(AoCAuxiliarySubprocessor.get_condition(line, - obj_ref, - enabling_research_id)) - - creatable_raw_api_object.add_raw_member("condition", - unlock_conditions, - "engine.util.create.CreatableGameEntity") - - # Placement modes - placement_modes = [] - if isinstance(line, GenieBuildingLineGroup): - # Buildings are placed on the map - # Place mode - obj_name = f"{game_entity_name}.CreatableGameEntity.Place" - place_raw_api_object = RawAPIObject(obj_name, - "Place", - dataset.nyan_api_objects) - place_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Place") - place_location = ForwardRef(line, - f"{game_entity_name}.CreatableGameEntity") - place_raw_api_object.set_location(place_location) - - # Tile snap distance (uses 1.0 for grid placement) - place_raw_api_object.add_raw_member("tile_snap_distance", - 1.0, - "engine.util.placement_mode.type.Place") - # Clearance size - clearance_size_x = current_unit["clearance_size_x"].value - clearance_size_y = current_unit["clearance_size_y"].value - place_raw_api_object.add_raw_member("clearance_size_x", - clearance_size_x, - "engine.util.placement_mode.type.Place") - place_raw_api_object.add_raw_member("clearance_size_y", - clearance_size_y, - "engine.util.placement_mode.type.Place") - - # Allow rotation - place_raw_api_object.add_raw_member("allow_rotation", - True, - "engine.util.placement_mode.type.Place") - - # Max elevation difference - elevation_mode = current_unit["elevation_mode"].value - if elevation_mode == 2: - max_elevation_difference = 0 - - elif elevation_mode == 3: - max_elevation_difference = 1 - - else: - max_elevation_difference = MemberSpecialValue.NYAN_INF - - place_raw_api_object.add_raw_member("max_elevation_difference", - max_elevation_difference, - "engine.util.placement_mode.type.Place") - - line.add_raw_api_object(place_raw_api_object) - - place_forward_ref = ForwardRef(line, obj_name) - placement_modes.append(place_forward_ref) - - if line.get_class_id() == 39: - # Gates - obj_name = f"{game_entity_name}.CreatableGameEntity.Replace" - replace_raw_api_object = RawAPIObject(obj_name, - "Replace", - dataset.nyan_api_objects) - replace_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Replace") - replace_location = ForwardRef(line, - f"{game_entity_name}.CreatableGameEntity") - replace_raw_api_object.set_location(replace_location) - - # Game entities (only stone wall) - wall_line_id = 117 - wall_line = dataset.building_lines[wall_line_id] - wall_name = name_lookup_dict[117][0] - game_entities = [ForwardRef(wall_line, wall_name)] - replace_raw_api_object.add_raw_member("game_entities", - game_entities, - "engine.util.placement_mode.type.Replace") - - line.add_raw_api_object(replace_raw_api_object) - - replace_forward_ref = ForwardRef(line, obj_name) - placement_modes.append(replace_forward_ref) - - else: - placement_modes.append( - dataset.nyan_api_objects["engine.util.placement_mode.type.Eject"]) - - # OwnStorage mode - obj_name = f"{game_entity_name}.CreatableGameEntity.OwnStorage" - own_storage_raw_api_object = RawAPIObject(obj_name, "OwnStorage", - dataset.nyan_api_objects) - own_storage_raw_api_object.add_raw_parent("engine.util.placement_mode.type.OwnStorage") - own_storage_location = ForwardRef(line, - f"{game_entity_name}.CreatableGameEntity") - own_storage_raw_api_object.set_location(own_storage_location) - - # Container - container_forward_ref = ForwardRef(train_location, - (f"{train_location_name}.Storage." - f"{train_location_name}Container")) - own_storage_raw_api_object.add_raw_member("container", - container_forward_ref, - "engine.util.placement_mode.type.OwnStorage") - - line.add_raw_api_object(own_storage_raw_api_object) - - own_storage_forward_ref = ForwardRef(line, obj_name) - placement_modes.append(own_storage_forward_ref) - - creatable_raw_api_object.add_raw_member("placement_modes", - placement_modes, - "engine.util.create.CreatableGameEntity") - - line.add_raw_api_object(creatable_raw_api_object) - line.add_raw_api_object(cost_raw_api_object) - - @staticmethod - def get_researchable_tech(tech_group: GenieTechEffectBundleGroup) -> None: - """ - Creates the ResearchableTech object for a Tech. - - :param tech_group: Tech group that is a technology. - :type tech_group: ...dataformat.converter_object.ConverterObjectGroup - """ - dataset = tech_group.data - research_location_id = tech_group.get_research_location_id() - research_location = dataset.building_lines[research_location_id] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - research_location_name = name_lookup_dict[research_location_id][0] - tech_name = tech_lookup_dict[tech_group.get_id()][0] - - obj_ref = f"{tech_name}.ResearchableTech" - obj_name = f"{tech_name}Researchable" - researchable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) - researchable_raw_api_object.add_raw_parent("engine.util.research.ResearchableTech") - - # Location of the object depends on whether it'a a unique tech or a normal tech - if tech_group.is_unique(): - # Add object to the Civ object - civ_id = tech_group.get_civilization() - civ = dataset.civ_groups[civ_id] - civ_name = civ_lookup_dict[civ_id][0] - - researchable_location = ForwardRef(civ, civ_name) - - else: - # Add object to the research location's Research ability - researchable_location = ForwardRef(research_location, - f"{research_location_name}.Research") - - researchable_raw_api_object.set_location(researchable_location) - - # Tech - tech_forward_ref = ForwardRef(tech_group, tech_name) - researchable_raw_api_object.add_raw_member("tech", - tech_forward_ref, - "engine.util.research.ResearchableTech") - - # Cost - cost_ref = f"{tech_name}.ResearchableTech.{tech_name}Cost" - cost_raw_api_object = RawAPIObject(cost_ref, - f"{tech_name}Cost", - dataset.nyan_api_objects) - cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") - tech_forward_ref = ForwardRef(tech_group, obj_ref) - cost_raw_api_object.set_location(tech_forward_ref) - - payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] - cost_raw_api_object.add_raw_member("payment_mode", - payment_mode, - "engine.util.cost.Cost") - - cost_amounts = [] - for resource_amount in tech_group.tech["research_resource_costs"].value: - resource_id = resource_amount["type_id"].value - resource = None - resource_name = "" - if resource_id == -1: - # Not a valid resource - continue - - if resource_id == 0: - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - resource_name = "Food" - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() - resource_name = "Wood" - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( - ) - resource_name = "Stone" - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() - resource_name = "Gold" - - else: - # Other resource ids are handled differently - continue - - # Skip resources that are only expected to be there - if not resource_amount["enabled"].value: - continue - - amount = resource_amount["amount"].value - - cost_amount_ref = f"{cost_ref}.{resource_name}Amount" - cost_amount = RawAPIObject(cost_amount_ref, - f"{resource_name}Amount", - dataset.nyan_api_objects) - cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") - cost_forward_ref = ForwardRef(tech_group, cost_ref) - cost_amount.set_location(cost_forward_ref) - - cost_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - cost_amount.add_raw_member("amount", - amount, - "engine.util.resource.ResourceAmount") - - cost_amount_forward_ref = ForwardRef(tech_group, cost_amount_ref) - cost_amounts.append(cost_amount_forward_ref) - tech_group.add_raw_api_object(cost_amount) - - cost_raw_api_object.add_raw_member("amount", - cost_amounts, - "engine.util.cost.type.ResourceCost") - - cost_forward_ref = ForwardRef(tech_group, cost_ref) - researchable_raw_api_object.add_raw_member("cost", - cost_forward_ref, - "engine.util.research.ResearchableTech") - - research_time = tech_group.tech["research_time"].value - researchable_raw_api_object.add_raw_member("research_time", - research_time, - "engine.util.research.ResearchableTech") - - # Create sound object - sound_ref = f"{tech_name}.ResearchableTech.Sound" - sound_raw_api_object = RawAPIObject(sound_ref, "ResearchSound", - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(tech_group, - f"{tech_name}.ResearchableTech") - sound_raw_api_object.set_location(sound_location) - - # AoE doesn't support sounds here, so this is empty - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - [], - "engine.util.sound.Sound") - - sound_forward_ref = ForwardRef(tech_group, sound_ref) - researchable_raw_api_object.add_raw_member("research_sounds", - [sound_forward_ref], - "engine.util.research.ResearchableTech") - - tech_group.add_raw_api_object(sound_raw_api_object) - - # Condition - unlock_conditions = [] - if tech_group.get_id() > -1: - unlock_conditions.extend(AoCAuxiliarySubprocessor.get_condition(tech_group, - obj_ref, - tech_group.get_id(), - top_level=True)) - - researchable_raw_api_object.add_raw_member("condition", - unlock_conditions, - "engine.util.research.ResearchableTech") - - tech_group.add_raw_api_object(researchable_raw_api_object) - tech_group.add_raw_api_object(cost_raw_api_object) - - @staticmethod - def get_condition( - converter_obj_group: ConverterObjectGroup, - obj_ref: str, - tech_id: int, - top_level: bool = False - ) -> list[ForwardRef]: - """ - Creates the condition for a creatable or researchable from tech - by recursively searching the required techs. - - :param converter_object: ConverterObjectGroup that the condition objects should be nested in. - :param obj_ref: Reference of converter_object inside the modpack. - :param tech_id: tech ID of a tech wth a conditional unlock. - :param top_level: True if the condition has subconditions, False otherwise. - """ - dataset = converter_obj_group.data - tech = dataset.genie_techs[tech_id] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - if not top_level and\ - (tech_id in dataset.initiated_techs.keys() or - (tech_id in dataset.tech_groups.keys() and - dataset.tech_groups[tech_id].is_researchable())): - # The tech condition is a building or a researchable tech - # and thus a literal. - if tech_id in dataset.initiated_techs.keys(): - initiated_tech = dataset.initiated_techs[tech_id] - building_id = initiated_tech.get_building_id() - building_name = name_lookup_dict[building_id][0] - literal_name = f"{building_name}Built" - literal_parent = "engine.util.logic.literal.type.GameEntityProgress" - - elif dataset.tech_groups[tech_id].is_researchable(): - tech_name = tech_lookup_dict[tech_id][0] - literal_name = f"{tech_name}Researched" - literal_parent = "engine.util.logic.literal.type.TechResearched" - - else: - raise ValueError("Required tech id {tech_id} is neither intiated nor researchable") - - literal_ref = f"{obj_ref}.{literal_name}" - literal_raw_api_object = RawAPIObject(literal_ref, - literal_name, - dataset.nyan_api_objects) - literal_raw_api_object.add_raw_parent(literal_parent) - literal_location = ForwardRef(converter_obj_group, obj_ref) - literal_raw_api_object.set_location(literal_location) - - if tech_id in dataset.initiated_techs.keys(): - building_line = dataset.unit_ref[building_id] - building_forward_ref = ForwardRef(building_line, building_name) - - # Building - literal_raw_api_object.add_raw_member("game_entity", - building_forward_ref, - literal_parent) - - # Progress - # ======================================================================= - progress_ref = f"{literal_ref}.ProgressStatus" - progress_raw_api_object = RawAPIObject(progress_ref, - "ProgressStatus", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress_status.ProgressStatus") - progress_location = ForwardRef(converter_obj_group, literal_ref) - progress_raw_api_object.set_location(progress_location) - - # Type - progress_type = dataset.nyan_api_objects["engine.util.progress_type.type.Construct"] - progress_raw_api_object.add_raw_member("progress_type", - progress_type, - "engine.util.progress_status.ProgressStatus") - - # Progress (building must be 100% constructed) - progress_raw_api_object.add_raw_member("progress", - 100, - "engine.util.progress_status.ProgressStatus") - - converter_obj_group.add_raw_api_object(progress_raw_api_object) - # ======================================================================= - progress_forward_ref = ForwardRef(converter_obj_group, progress_ref) - literal_raw_api_object.add_raw_member("progress_status", - progress_forward_ref, - literal_parent) - - elif dataset.tech_groups[tech_id].is_researchable(): - tech_group = dataset.tech_groups[tech_id] - tech_forward_ref = ForwardRef(tech_group, tech_name) - literal_raw_api_object.add_raw_member("tech", - tech_forward_ref, - literal_parent) - - # LiteralScope - # ========================================================================== - scope_ref = f"{literal_ref}.LiteralScope" - scope_raw_api_object = RawAPIObject(scope_ref, - "LiteralScope", - dataset.nyan_api_objects) - scope_raw_api_object.add_raw_parent("engine.util.logic.literal_scope.type.Any") - scope_location = ForwardRef(converter_obj_group, literal_ref) - scope_raw_api_object.set_location(scope_location) - - scope_diplomatic_stances = [ - dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"] - ] - scope_raw_api_object.add_raw_member("stances", - scope_diplomatic_stances, - "engine.util.logic.literal_scope.LiteralScope") - - converter_obj_group.add_raw_api_object(scope_raw_api_object) - # ========================================================================== - scope_forward_ref = ForwardRef(converter_obj_group, scope_ref) - literal_raw_api_object.add_raw_member("scope", - scope_forward_ref, - "engine.util.logic.literal.Literal") - - literal_raw_api_object.add_raw_member("only_once", - True, - "engine.util.logic.LogicElement") - - converter_obj_group.add_raw_api_object(literal_raw_api_object) - literal_forward_ref = ForwardRef(converter_obj_group, literal_ref) - - return [literal_forward_ref] - - else: - # The tech condition has other requirements that need to be resolved - - # Find required techs for the current tech - assoc_tech_id_members = [] - assoc_tech_id_members.extend(tech["required_techs"].value) - required_tech_count = tech["required_tech_count"].value - - # Remove tech ids that are invalid or those we don't use - relevant_ids = [] - for tech_id_member in assoc_tech_id_members: - required_tech_id = tech_id_member.value - if required_tech_id == -1: - continue - - if required_tech_id == 104: - # Skip Dark Age tech - required_tech_count -= 1 - continue - - if required_tech_id in dataset.civ_boni.keys(): - continue - - relevant_ids.append(required_tech_id) - - if len(relevant_ids) == 0: - return [] - - if len(relevant_ids) == 1: - # If there's only one required tech we don't need a gate - # we can just return the logic element of the only required tech - required_tech_id = relevant_ids[0] - return AoCAuxiliarySubprocessor.get_condition(converter_obj_group, - obj_ref, - required_tech_id) - - gate_ref = f"{obj_ref}.UnlockCondition" - gate_raw_api_object = RawAPIObject(gate_ref, - "UnlockCondition", - dataset.nyan_api_objects) - - if required_tech_count == len(relevant_ids): - gate_raw_api_object.add_raw_parent("engine.util.logic.gate.type.AND") - gate_location = ForwardRef(converter_obj_group, obj_ref) - - else: - gate_raw_api_object.add_raw_parent("engine.util.logic.gate.type.SUBSETMIN") - gate_location = ForwardRef(converter_obj_group, obj_ref) - - gate_raw_api_object.add_raw_member("size", - required_tech_count, - "engine.util.logic.gate.type.SUBSETMIN") - - gate_raw_api_object.set_location(gate_location) - - # Once unlocked, a creatable/researchable is unlocked forever - gate_raw_api_object.add_raw_member("only_once", - True, - "engine.util.logic.LogicElement") - - # Get requirements from subtech recursively - inputs = [] - for required_tech_id in relevant_ids: - required = AoCAuxiliarySubprocessor.get_condition(converter_obj_group, - gate_ref, - required_tech_id) - inputs.extend(required) - - gate_raw_api_object.add_raw_member("inputs", - inputs, - "engine.util.logic.gate.LogicGate") - - converter_obj_group.add_raw_api_object(gate_raw_api_object) - gate_forward_ref = ForwardRef(converter_obj_group, gate_ref) - return [gate_forward_ref] + get_creatable_game_entity = staticmethod(get_creatable_game_entity) + get_researchable_tech = staticmethod(get_researchable_tech) + get_condition = staticmethod(get_condition) From 6888c4720173ce53af673f1d730577994b78e922 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 29 May 2025 11:48:02 +0200 Subject: [PATCH 113/163] convert: Refactor AoCCivSubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../conversion/aoc/civ/CMakeLists.txt | 10 + .../processor/conversion/aoc/civ/__init__.py | 5 + .../processor/conversion/aoc/civ/civ_bonus.py | 109 +++ .../processor/conversion/aoc/civ/modifiers.py | 28 + .../conversion/aoc/civ/starting_resources.py | 141 ++++ .../processor/conversion/aoc/civ/tech_tree.py | 201 ++++++ .../conversion/aoc/civ/unique_techs.py | 87 +++ .../conversion/aoc/civ/unique_units.py | 92 +++ .../processor/conversion/aoc/civ/util.py | 65 ++ .../conversion/aoc/civ_subprocessor.py | 632 +----------------- 11 files changed, 759 insertions(+), 612 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/civ/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/civ/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/civ/civ_bonus.py create mode 100644 openage/convert/processor/conversion/aoc/civ/modifiers.py create mode 100644 openage/convert/processor/conversion/aoc/civ/starting_resources.py create mode 100644 openage/convert/processor/conversion/aoc/civ/tech_tree.py create mode 100644 openage/convert/processor/conversion/aoc/civ/unique_techs.py create mode 100644 openage/convert/processor/conversion/aoc/civ/unique_units.py create mode 100644 openage/convert/processor/conversion/aoc/civ/util.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index d26b6749b7..0c98c461b6 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -19,5 +19,6 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(auxiliary) +add_subdirectory(civ) add_subdirectory(effect) add_subdirectory(resistance) diff --git a/openage/convert/processor/conversion/aoc/civ/CMakeLists.txt b/openage/convert/processor/conversion/aoc/civ/CMakeLists.txt new file mode 100644 index 0000000000..b14c2e9161 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/civ/CMakeLists.txt @@ -0,0 +1,10 @@ +add_py_modules( + __init__.py + civ_bonus.py + modifiers.py + starting_resources.py + tech_tree.py + unique_techs.py + unique_units.py + util.py +) diff --git a/openage/convert/processor/conversion/aoc/civ/__init__.py b/openage/convert/processor/conversion/aoc/civ/__init__.py new file mode 100644 index 0000000000..4c766c2781 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/civ/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches and modifiers for civs. +""" diff --git a/openage/convert/processor/conversion/aoc/civ/civ_bonus.py b/openage/convert/processor/conversion/aoc/civ/civ_bonus.py new file mode 100644 index 0000000000..b36152dc8b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/civ/civ_bonus.py @@ -0,0 +1,109 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ bonuses. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..tech_subprocessor import AoCTechSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def setup_civ_bonus(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Returns global modifiers of a civ. + + :param civ_group: Civ group representing an AoC civilization. + """ + patches: list = [] + + civ_id = civ_group.get_id() + dataset = civ_group.data + + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + civ_name = civ_lookup_dict[civ_id][0] + + # key: tech_id; value patched in patches + tech_patches = {} + + for civ_bonus in civ_group.civ_boni.values(): + if not civ_bonus.replaces_researchable_tech(): + bonus_patches = AoCTechSubprocessor.get_patches(civ_bonus) + + # civ boni might be unlocked by age ups. if so, patch them into the age up + # patches are queued here + required_tech_count = civ_bonus.tech["required_tech_count"].value + if required_tech_count > 0 and len(bonus_patches) > 0: + if required_tech_count == 1: + tech_id = civ_bonus.tech["required_techs"][0].value + + elif required_tech_count == 2: + tech_id = civ_bonus.tech["required_techs"][1].value + + if tech_id == 104: + # Skip Dark Age; it is not a tech in openage + patches.extend(bonus_patches) + + elif tech_id in tech_patches: + tech_patches[tech_id].extend(bonus_patches) + + else: + tech_patches[tech_id] = bonus_patches + + else: + patches.extend(bonus_patches) + + for tech_id, patches in tech_patches.items(): + tech_group = dataset.tech_groups[tech_id] + tech_name = tech_lookup_dict[tech_id][0] + + patch_target_ref = f"{tech_name}" + patch_target_forward_ref = ForwardRef(tech_group, patch_target_ref) + + # Wrapper + wrapper_name = f"{tech_name}CivBonusWrapper" + wrapper_ref = f"{civ_name}.{wrapper_name}" + wrapper_location = ForwardRef(civ_group, civ_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"{tech_name}CivBonus" + nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(civ_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) + + nyan_patch_raw_api_object.add_raw_patch_member("updates", + patches, + "engine.util.tech.Tech", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + civ_group.add_raw_api_object(wrapper_raw_api_object) + civ_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + return patches diff --git a/openage/convert/processor/conversion/aoc/civ/modifiers.py b/openage/convert/processor/conversion/aoc/civ/modifiers.py new file mode 100644 index 0000000000..0a78114047 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/civ/modifiers.py @@ -0,0 +1,28 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ modifiers. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def get_modifiers(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Returns global modifiers of a civ. + + :param civ_group: Civ group representing an AoC civilization. + """ + modifiers = [] + + for civ_bonus in civ_group.civ_boni.values(): + if civ_bonus.replaces_researchable_tech(): + # TODO: instant tech research modifier + pass + + return modifiers diff --git a/openage/convert/processor/conversion/aoc/civ/starting_resources.py b/openage/convert/processor/conversion/aoc/civ/starting_resources.py new file mode 100644 index 0000000000..e32409f6bb --- /dev/null +++ b/openage/convert/processor/conversion/aoc/civ/starting_resources.py @@ -0,0 +1,141 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ starting resources. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def get_starting_resources(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Returns the starting resources of a civ. + + :param civ_group: Civ group representing an AoC civilization. + """ + resource_amounts = [] + + civ_id = civ_group.get_id() + dataset = civ_group.data + + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + civ_name = civ_lookup_dict[civ_id][0] + + # Find starting resource amounts + food_amount = civ_group.civ["resources"][91].value + wood_amount = civ_group.civ["resources"][92].value + gold_amount = civ_group.civ["resources"][93].value + stone_amount = civ_group.civ["resources"][94].value + + # Find civ unique starting resources + tech_tree = civ_group.get_tech_tree_effects() + for effect in tech_tree: + type_id = effect.get_type() + + if type_id != 1: + continue + + resource_id = effect["attr_a"].value + amount = effect["attr_d"].value + if resource_id == 91: + food_amount += amount + + elif resource_id == 92: + wood_amount += amount + + elif resource_id == 93: + gold_amount += amount + + elif resource_id == 94: + stone_amount += amount + + food_ref = f"{civ_name}.FoodStartingAmount" + food_raw_api_object = RawAPIObject(food_ref, "FoodStartingAmount", + dataset.nyan_api_objects) + food_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + food_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + food_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + food_raw_api_object.add_raw_member("amount", + food_amount, + "engine.util.resource.ResourceAmount") + + food_forward_ref = ForwardRef(civ_group, food_ref) + resource_amounts.append(food_forward_ref) + + wood_ref = f"{civ_name}.WoodStartingAmount" + wood_raw_api_object = RawAPIObject(wood_ref, "WoodStartingAmount", + dataset.nyan_api_objects) + wood_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + wood_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() + wood_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + wood_raw_api_object.add_raw_member("amount", + wood_amount, + "engine.util.resource.ResourceAmount") + + wood_forward_ref = ForwardRef(civ_group, wood_ref) + resource_amounts.append(wood_forward_ref) + + gold_ref = f"{civ_name}.GoldStartingAmount" + gold_raw_api_object = RawAPIObject(gold_ref, "GoldStartingAmount", + dataset.nyan_api_objects) + gold_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + gold_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() + gold_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + gold_raw_api_object.add_raw_member("amount", + gold_amount, + "engine.util.resource.ResourceAmount") + + gold_forward_ref = ForwardRef(civ_group, gold_ref) + resource_amounts.append(gold_forward_ref) + + stone_ref = f"{civ_name}.StoneStartingAmount" + stone_raw_api_object = RawAPIObject(stone_ref, "StoneStartingAmount", + dataset.nyan_api_objects) + stone_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + stone_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object() + stone_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + stone_raw_api_object.add_raw_member("amount", + stone_amount, + "engine.util.resource.ResourceAmount") + + stone_forward_ref = ForwardRef(civ_group, stone_ref) + resource_amounts.append(stone_forward_ref) + + civ_group.add_raw_api_object(food_raw_api_object) + civ_group.add_raw_api_object(wood_raw_api_object) + civ_group.add_raw_api_object(gold_raw_api_object) + civ_group.add_raw_api_object(stone_raw_api_object) + + return resource_amounts diff --git a/openage/convert/processor/conversion/aoc/civ/tech_tree.py b/openage/convert/processor/conversion/aoc/civ/tech_tree.py new file mode 100644 index 0000000000..de4648db87 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/civ/tech_tree.py @@ -0,0 +1,201 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ tech tree. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..tech_subprocessor import AoCTechSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def setup_tech_tree(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Patches standard techs and units out of Research and Create. + + :param civ_group: Civ group representing an AoC civilization. + """ + patches = [] + + civ_id = civ_group.get_id() + dataset = civ_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + civ_name = civ_lookup_dict[civ_id][0] + + disabled_techs = {} + disabled_entities = {} + + tech_tree = civ_group.get_tech_tree_effects() + for effect in tech_tree: + type_id = effect.get_type() + + if type_id == 101: + patches.extend(AoCTechSubprocessor.tech_cost_modify_effect(civ_group, effect)) + continue + + if type_id == 103: + patches.extend(AoCTechSubprocessor.tech_time_modify_effect(civ_group, effect)) + continue + + if type_id != 102: + continue + + # Get tech id + tech_id = int(effect["attr_d"].value) + + # Check what the purpose of the tech is + if tech_id in dataset.unit_unlocks.keys(): + unlock_tech = dataset.unit_unlocks[tech_id] + unlocked_line = unlock_tech.get_unlocked_line() + train_location_id = unlocked_line.get_train_location_id() + + if isinstance(unlocked_line, GenieBuildingLineGroup): + train_location = dataset.unit_lines[train_location_id] + + else: + train_location = dataset.building_lines[train_location_id] + + if train_location in disabled_entities: + disabled_entities[train_location].append(unlocked_line) + + else: + disabled_entities[train_location] = [unlocked_line] + + elif tech_id in dataset.civ_boni.keys(): + # Disables civ boni of other civs + continue + + elif tech_id in dataset.tech_groups.keys(): + tech_group = dataset.tech_groups[tech_id] + if tech_group.is_researchable(): + research_location_id = tech_group.get_research_location_id() + research_location = dataset.building_lines[research_location_id] + + if research_location in disabled_techs: + disabled_techs[research_location].append(tech_group) + + else: + disabled_techs[research_location] = [tech_group] + + else: + continue + + for train_location, entities in disabled_entities.items(): + train_location_id = train_location.get_head_unit_id() + train_location_name = name_lookup_dict[train_location_id][0] + + patch_target_ref = f"{train_location_name}.Create" + patch_target_forward_ref = ForwardRef(train_location, patch_target_ref) + + # Wrapper + wrapper_name = f"Disable{train_location_name}CreatablesWrapper" + wrapper_ref = f"{civ_name}.{wrapper_name}" + wrapper_location = ForwardRef(civ_group, civ_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Disable{train_location_name}Creatables" + nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(civ_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) + + entities_forward_refs = [] + for entity in entities: + entity_id = entity.get_head_unit_id() + game_entity_name = name_lookup_dict[entity_id][0] + + disabled_ref = f"{game_entity_name}.CreatableGameEntity" + disabled_forward_ref = ForwardRef(entity, disabled_ref) + entities_forward_refs.append(disabled_forward_ref) + + nyan_patch_raw_api_object.add_raw_patch_member("creatables", + entities_forward_refs, + "engine.ability.type.Create", + MemberOperator.SUBTRACT) + + patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + civ_group.add_raw_api_object(wrapper_raw_api_object) + civ_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + for research_location, techs in disabled_techs.items(): + research_location_id = research_location.get_head_unit_id() + research_location_name = name_lookup_dict[research_location_id][0] + + patch_target_ref = f"{research_location_name}.Research" + patch_target_forward_ref = ForwardRef(research_location, patch_target_ref) + + # Wrapper + wrapper_name = f"Disable{research_location_name}ResearchablesWrapper" + wrapper_ref = f"{civ_name}.{wrapper_name}" + wrapper_location = ForwardRef(civ_group, civ_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Disable{research_location_name}Researchables" + nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(civ_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) + + entities_forward_refs = [] + for tech_group in techs: + tech_id = tech_group.get_id() + tech_name = tech_lookup_dict[tech_id][0] + + disabled_ref = f"{tech_name}.ResearchableTech" + disabled_forward_ref = ForwardRef(tech_group, disabled_ref) + entities_forward_refs.append(disabled_forward_ref) + + nyan_patch_raw_api_object.add_raw_patch_member("researchables", + entities_forward_refs, + "engine.ability.type.Research", + MemberOperator.SUBTRACT) + + patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + civ_group.add_raw_api_object(wrapper_raw_api_object) + civ_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + return patches diff --git a/openage/convert/processor/conversion/aoc/civ/unique_techs.py b/openage/convert/processor/conversion/aoc/civ/unique_techs.py new file mode 100644 index 0000000000..f1f6bb480a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/civ/unique_techs.py @@ -0,0 +1,87 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ unique techs. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def setup_unique_techs(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Patches the unique techs into their research location. + + :param civ_group: Civ group representing an AoC civilization. + """ + patches = [] + + civ_id = civ_group.get_id() + dataset = civ_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + civ_name = civ_lookup_dict[civ_id][0] + + for unique_tech in civ_group.unique_techs.values(): + tech_id = unique_tech.get_id() + tech_name = tech_lookup_dict[tech_id][0] + + # Get train location of line + research_location_id = unique_tech.get_research_location_id() + research_location = dataset.building_lines[research_location_id] + research_location_name = name_lookup_dict[research_location_id][0] + + patch_target_ref = f"{research_location_name}.Research" + patch_target_forward_ref = ForwardRef(research_location, patch_target_ref) + + # Wrapper + wrapper_name = f"Add{tech_name}ResearchableWrapper" + wrapper_ref = f"{civ_name}.{wrapper_name}" + wrapper_location = ForwardRef(civ_group, civ_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Add{tech_name}Researchable" + nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(civ_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) + + # Add creatable + researchable_ref = f"{tech_name}.ResearchableTech" + researchable_forward_ref = ForwardRef(unique_tech, researchable_ref) + nyan_patch_raw_api_object.add_raw_patch_member("researchables", + [researchable_forward_ref], + "engine.ability.type.Research", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + civ_group.add_raw_api_object(wrapper_raw_api_object) + civ_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + return patches diff --git a/openage/convert/processor/conversion/aoc/civ/unique_units.py b/openage/convert/processor/conversion/aoc/civ/unique_units.py new file mode 100644 index 0000000000..5891cb72cf --- /dev/null +++ b/openage/convert/processor/conversion/aoc/civ/unique_units.py @@ -0,0 +1,92 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ unique units. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def setup_unique_units(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Patches the unique units into their train location. + + :param civ_group: Civ group representing an AoC civilization. + """ + patches = [] + + civ_id = civ_group.get_id() + dataset = civ_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + civ_name = civ_lookup_dict[civ_id][0] + + for unique_line in civ_group.unique_entities.values(): + head_unit_id = unique_line.get_head_unit_id() + game_entity_name = name_lookup_dict[head_unit_id][0] + + # Get train location of line + train_location_id = unique_line.get_train_location_id() + if isinstance(unique_line, GenieBuildingLineGroup): + train_location = dataset.unit_lines[train_location_id] + train_location_name = name_lookup_dict[train_location_id][0] + + else: + train_location = dataset.building_lines[train_location_id] + train_location_name = name_lookup_dict[train_location_id][0] + + patch_target_ref = f"{train_location_name}.Create" + patch_target_forward_ref = ForwardRef(train_location, patch_target_ref) + + # Wrapper + wrapper_name = f"Add{game_entity_name}CreatableWrapper" + wrapper_ref = f"{civ_name}.{wrapper_name}" + wrapper_location = ForwardRef(civ_group, civ_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Add{game_entity_name}Creatable" + nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(civ_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) + + # Add creatable + creatable_ref = f"{game_entity_name}.CreatableGameEntity" + creatable_forward_ref = ForwardRef(unique_line, creatable_ref) + nyan_patch_raw_api_object.add_raw_patch_member("creatables", + [creatable_forward_ref], + "engine.ability.type.Create", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + civ_group.add_raw_api_object(wrapper_raw_api_object) + civ_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + return patches diff --git a/openage/convert/processor/conversion/aoc/civ/util.py b/openage/convert/processor/conversion/aoc/civ/util.py new file mode 100644 index 0000000000..37da3e476c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/civ/util.py @@ -0,0 +1,65 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Helper functions for AoC civs. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.combined_sprite import CombinedSprite +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def create_animation( + line: GenieGameEntityGroup, + animation_id: int, + nyan_patch_ref: str, + animation_name: str, + filename_prefix: str +) -> ForwardRef: + """ + Creates an animation for an ability. + + :param line: ConverterObjectGroup that the animation object is added to. + :param animation_id: ID of the animation in the dataset. + :param nyan_patch_ref: Reference of the patch object the animation is nested in. + :param animation_name: Name prefix for the animation object. + :param filename_prefix: Prefix for the animation PNG and sprite files. + """ + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + animation_ref = f"{nyan_patch_ref}.{animation_name}Animation" + animation_obj_name = f"{animation_name}Animation" + animation_raw_api_object = RawAPIObject(animation_ref, animation_obj_name, + dataset.nyan_api_objects) + animation_raw_api_object.add_raw_parent("engine.util.graphics.Animation") + animation_location = ForwardRef(line, nyan_patch_ref) + animation_raw_api_object.set_location(animation_location) + + if animation_id in dataset.combined_sprites.keys(): + animation_sprite = dataset.combined_sprites[animation_id] + + else: + animation_filename = f"{filename_prefix}{name_lookup_dict[line.get_head_unit_id()][1]}" + animation_sprite = CombinedSprite(animation_id, + animation_filename, + dataset) + dataset.combined_sprites.update({animation_sprite.get_id(): animation_sprite}) + + animation_sprite.add_reference(animation_raw_api_object) + + animation_raw_api_object.add_raw_member("sprite", animation_sprite, + "engine.util.graphics.Animation") + + line.add_raw_api_object(animation_raw_api_object) + + animation_forward_ref = ForwardRef(line, animation_ref) + + return animation_forward_ref diff --git a/openage/convert/processor/conversion/aoc/civ_subprocessor.py b/openage/convert/processor/conversion/aoc/civ_subprocessor.py index 30eea447f9..e5877a5e5f 100644 --- a/openage/convert/processor/conversion/aoc/civ_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/civ_subprocessor.py @@ -1,6 +1,4 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-statements,too-many-branches +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates patches and modifiers for civs. @@ -8,17 +6,18 @@ from __future__ import annotations import typing -from .....nyan.nyan_structs import MemberOperator -from ....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup -from ....entity_object.conversion.combined_sprite import CombinedSprite -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups from ....value_object.conversion.forward_ref import ForwardRef from .tech_subprocessor import AoCTechSubprocessor +from .civ.civ_bonus import setup_civ_bonus +from .civ.modifiers import get_modifiers +from .civ.starting_resources import get_starting_resources +from .civ.tech_tree import setup_tech_tree +from .civ.unique_techs import setup_unique_techs +from .civ.unique_units import setup_unique_units +from .civ.util import create_animation if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from ....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup class AoCCivSubprocessor: @@ -34,612 +33,21 @@ def get_civ_setup(cls, civ_group: GenieCivilizationGroup) -> list[ForwardRef]: """ patches = [] - patches.extend(cls.setup_unique_units(civ_group)) - patches.extend(cls.setup_unique_techs(civ_group)) - patches.extend(cls.setup_tech_tree(civ_group)) - patches.extend(cls.setup_civ_bonus(civ_group)) + patches.extend(AoCCivSubprocessor.setup_unique_units(civ_group)) + patches.extend(AoCCivSubprocessor.setup_unique_techs(civ_group)) + patches.extend(AoCCivSubprocessor.setup_tech_tree(civ_group)) + patches.extend(AoCCivSubprocessor.setup_civ_bonus(civ_group)) if len(civ_group.get_team_bonus_effects()) > 0: patches.extend(AoCTechSubprocessor.get_patches(civ_group.team_bonus)) return patches - @ classmethod - def get_modifiers(cls, civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Returns global modifiers of a civ. - """ - modifiers = [] - - for civ_bonus in civ_group.civ_boni.values(): - if civ_bonus.replaces_researchable_tech(): - # TODO: instant tech research modifier - pass - - return modifiers - - @ staticmethod - def get_starting_resources(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Returns the starting resources of a civ. - """ - resource_amounts = [] - - civ_id = civ_group.get_id() - dataset = civ_group.data - - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - civ_name = civ_lookup_dict[civ_id][0] - - # Find starting resource amounts - food_amount = civ_group.civ["resources"][91].value - wood_amount = civ_group.civ["resources"][92].value - gold_amount = civ_group.civ["resources"][93].value - stone_amount = civ_group.civ["resources"][94].value - - # Find civ unique starting resources - tech_tree = civ_group.get_tech_tree_effects() - for effect in tech_tree: - type_id = effect.get_type() - - if type_id != 1: - continue - - resource_id = effect["attr_a"].value - amount = effect["attr_d"].value - if resource_id == 91: - food_amount += amount - - elif resource_id == 92: - wood_amount += amount - - elif resource_id == 93: - gold_amount += amount - - elif resource_id == 94: - stone_amount += amount - - food_ref = f"{civ_name}.FoodStartingAmount" - food_raw_api_object = RawAPIObject(food_ref, "FoodStartingAmount", - dataset.nyan_api_objects) - food_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - food_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - food_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - food_raw_api_object.add_raw_member("amount", - food_amount, - "engine.util.resource.ResourceAmount") - - food_forward_ref = ForwardRef(civ_group, food_ref) - resource_amounts.append(food_forward_ref) - - wood_ref = f"{civ_name}.WoodStartingAmount" - wood_raw_api_object = RawAPIObject(wood_ref, "WoodStartingAmount", - dataset.nyan_api_objects) - wood_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - wood_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() - wood_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - wood_raw_api_object.add_raw_member("amount", - wood_amount, - "engine.util.resource.ResourceAmount") - - wood_forward_ref = ForwardRef(civ_group, wood_ref) - resource_amounts.append(wood_forward_ref) - - gold_ref = f"{civ_name}.GoldStartingAmount" - gold_raw_api_object = RawAPIObject(gold_ref, "GoldStartingAmount", - dataset.nyan_api_objects) - gold_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - gold_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() - gold_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - gold_raw_api_object.add_raw_member("amount", - gold_amount, - "engine.util.resource.ResourceAmount") - - gold_forward_ref = ForwardRef(civ_group, gold_ref) - resource_amounts.append(gold_forward_ref) - - stone_ref = f"{civ_name}.StoneStartingAmount" - stone_raw_api_object = RawAPIObject(stone_ref, "StoneStartingAmount", - dataset.nyan_api_objects) - stone_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - stone_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object() - stone_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - stone_raw_api_object.add_raw_member("amount", - stone_amount, - "engine.util.resource.ResourceAmount") - - stone_forward_ref = ForwardRef(civ_group, stone_ref) - resource_amounts.append(stone_forward_ref) - - civ_group.add_raw_api_object(food_raw_api_object) - civ_group.add_raw_api_object(wood_raw_api_object) - civ_group.add_raw_api_object(gold_raw_api_object) - civ_group.add_raw_api_object(stone_raw_api_object) - - return resource_amounts - - @ classmethod - def setup_civ_bonus(cls, civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Returns global modifiers of a civ. - """ - patches = [] - - civ_id = civ_group.get_id() - dataset = civ_group.data - - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - civ_name = civ_lookup_dict[civ_id][0] - - # key: tech_id; value patched in patches - tech_patches = {} - - for civ_bonus in civ_group.civ_boni.values(): - if not civ_bonus.replaces_researchable_tech(): - bonus_patches = AoCTechSubprocessor.get_patches(civ_bonus) - - # civ boni might be unlocked by age ups. if so, patch them into the age up - # patches are queued here - required_tech_count = civ_bonus.tech["required_tech_count"].value - if required_tech_count > 0 and len(bonus_patches) > 0: - if required_tech_count == 1: - tech_id = civ_bonus.tech["required_techs"][0].value - - elif required_tech_count == 2: - tech_id = civ_bonus.tech["required_techs"][1].value - - if tech_id == 104: - # Skip Dark Age; it is not a tech in openage - patches.extend(bonus_patches) - - elif tech_id in tech_patches: - tech_patches[tech_id].extend(bonus_patches) - - else: - tech_patches[tech_id] = bonus_patches - - else: - patches.extend(bonus_patches) - - for tech_id, patches in tech_patches.items(): - tech_group = dataset.tech_groups[tech_id] - tech_name = tech_lookup_dict[tech_id][0] - - patch_target_ref = f"{tech_name}" - patch_target_forward_ref = ForwardRef(tech_group, patch_target_ref) - - # Wrapper - wrapper_name = f"{tech_name}CivBonusWrapper" - wrapper_ref = f"{civ_name}.{wrapper_name}" - wrapper_location = ForwardRef(civ_group, civ_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"{tech_name}CivBonus" - nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(civ_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) - - nyan_patch_raw_api_object.add_raw_patch_member("updates", - patches, - "engine.util.tech.Tech", - MemberOperator.ADD) - - patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - civ_group.add_raw_api_object(wrapper_raw_api_object) - civ_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - return patches - - @ staticmethod - def setup_unique_units(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Patches the unique units into their train location. - """ - patches = [] - - civ_id = civ_group.get_id() - dataset = civ_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - civ_name = civ_lookup_dict[civ_id][0] - - for unique_line in civ_group.unique_entities.values(): - head_unit_id = unique_line.get_head_unit_id() - game_entity_name = name_lookup_dict[head_unit_id][0] - - # Get train location of line - train_location_id = unique_line.get_train_location_id() - if isinstance(unique_line, GenieBuildingLineGroup): - train_location = dataset.unit_lines[train_location_id] - train_location_name = name_lookup_dict[train_location_id][0] - - else: - train_location = dataset.building_lines[train_location_id] - train_location_name = name_lookup_dict[train_location_id][0] - - patch_target_ref = f"{train_location_name}.Create" - patch_target_forward_ref = ForwardRef(train_location, patch_target_ref) - - # Wrapper - wrapper_name = f"Add{game_entity_name}CreatableWrapper" - wrapper_ref = f"{civ_name}.{wrapper_name}" - wrapper_location = ForwardRef(civ_group, civ_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Add{game_entity_name}Creatable" - nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(civ_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) - - # Add creatable - creatable_ref = f"{game_entity_name}.CreatableGameEntity" - creatable_forward_ref = ForwardRef(unique_line, creatable_ref) - nyan_patch_raw_api_object.add_raw_patch_member("creatables", - [creatable_forward_ref], - "engine.ability.type.Create", - MemberOperator.ADD) - - patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - civ_group.add_raw_api_object(wrapper_raw_api_object) - civ_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - return patches - - @ staticmethod - def setup_unique_techs(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Patches the unique techs into their research location. - """ - patches = [] - - civ_id = civ_group.get_id() - dataset = civ_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - civ_name = civ_lookup_dict[civ_id][0] - - for unique_tech in civ_group.unique_techs.values(): - tech_id = unique_tech.get_id() - tech_name = tech_lookup_dict[tech_id][0] - - # Get train location of line - research_location_id = unique_tech.get_research_location_id() - research_location = dataset.building_lines[research_location_id] - research_location_name = name_lookup_dict[research_location_id][0] - - patch_target_ref = f"{research_location_name}.Research" - patch_target_forward_ref = ForwardRef(research_location, patch_target_ref) - - # Wrapper - wrapper_name = f"Add{tech_name}ResearchableWrapper" - wrapper_ref = f"{civ_name}.{wrapper_name}" - wrapper_location = ForwardRef(civ_group, civ_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Add{tech_name}Researchable" - nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(civ_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) - - # Add creatable - researchable_ref = f"{tech_name}.ResearchableTech" - researchable_forward_ref = ForwardRef(unique_tech, researchable_ref) - nyan_patch_raw_api_object.add_raw_patch_member("researchables", - [researchable_forward_ref], - "engine.ability.type.Research", - MemberOperator.ADD) - - patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - civ_group.add_raw_api_object(wrapper_raw_api_object) - civ_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - return patches - - @ staticmethod - def setup_tech_tree(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Patches standard techs and units out of Research and Create. - """ - patches = [] - - civ_id = civ_group.get_id() - dataset = civ_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - civ_name = civ_lookup_dict[civ_id][0] - - disabled_techs = {} - disabled_entities = {} - - tech_tree = civ_group.get_tech_tree_effects() - for effect in tech_tree: - type_id = effect.get_type() - - if type_id == 101: - patches.extend(AoCTechSubprocessor.tech_cost_modify_effect(civ_group, effect)) - continue - - if type_id == 103: - patches.extend(AoCTechSubprocessor.tech_time_modify_effect(civ_group, effect)) - continue - - if type_id != 102: - continue - - # Get tech id - tech_id = int(effect["attr_d"].value) - - # Check what the purpose of the tech is - if tech_id in dataset.unit_unlocks.keys(): - unlock_tech = dataset.unit_unlocks[tech_id] - unlocked_line = unlock_tech.get_unlocked_line() - train_location_id = unlocked_line.get_train_location_id() - - if isinstance(unlocked_line, GenieBuildingLineGroup): - train_location = dataset.unit_lines[train_location_id] - - else: - train_location = dataset.building_lines[train_location_id] - - if train_location in disabled_entities: - disabled_entities[train_location].append(unlocked_line) - - else: - disabled_entities[train_location] = [unlocked_line] - - elif tech_id in dataset.civ_boni.keys(): - # Disables civ boni of other civs - continue - - elif tech_id in dataset.tech_groups.keys(): - tech_group = dataset.tech_groups[tech_id] - if tech_group.is_researchable(): - research_location_id = tech_group.get_research_location_id() - research_location = dataset.building_lines[research_location_id] - - if research_location in disabled_techs: - disabled_techs[research_location].append(tech_group) - - else: - disabled_techs[research_location] = [tech_group] - - else: - continue - - for train_location, entities in disabled_entities.items(): - train_location_id = train_location.get_head_unit_id() - train_location_name = name_lookup_dict[train_location_id][0] - - patch_target_ref = f"{train_location_name}.Create" - patch_target_forward_ref = ForwardRef(train_location, patch_target_ref) - - # Wrapper - wrapper_name = f"Disable{train_location_name}CreatablesWrapper" - wrapper_ref = f"{civ_name}.{wrapper_name}" - wrapper_location = ForwardRef(civ_group, civ_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Disable{train_location_name}Creatables" - nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(civ_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) - - entities_forward_refs = [] - for entity in entities: - entity_id = entity.get_head_unit_id() - game_entity_name = name_lookup_dict[entity_id][0] - - disabled_ref = f"{game_entity_name}.CreatableGameEntity" - disabled_forward_ref = ForwardRef(entity, disabled_ref) - entities_forward_refs.append(disabled_forward_ref) - - nyan_patch_raw_api_object.add_raw_patch_member("creatables", - entities_forward_refs, - "engine.ability.type.Create", - MemberOperator.SUBTRACT) - - patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - civ_group.add_raw_api_object(wrapper_raw_api_object) - civ_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - for research_location, techs in disabled_techs.items(): - research_location_id = research_location.get_head_unit_id() - research_location_name = name_lookup_dict[research_location_id][0] - - patch_target_ref = f"{research_location_name}.Research" - patch_target_forward_ref = ForwardRef(research_location, patch_target_ref) - - # Wrapper - wrapper_name = f"Disable{research_location_name}ResearchablesWrapper" - wrapper_ref = f"{civ_name}.{wrapper_name}" - wrapper_location = ForwardRef(civ_group, civ_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Disable{research_location_name}Researchables" - nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(civ_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) - - entities_forward_refs = [] - for tech_group in techs: - tech_id = tech_group.get_id() - tech_name = tech_lookup_dict[tech_id][0] - - disabled_ref = f"{tech_name}.ResearchableTech" - disabled_forward_ref = ForwardRef(tech_group, disabled_ref) - entities_forward_refs.append(disabled_forward_ref) - - nyan_patch_raw_api_object.add_raw_patch_member("researchables", - entities_forward_refs, - "engine.ability.type.Research", - MemberOperator.SUBTRACT) - - patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - civ_group.add_raw_api_object(wrapper_raw_api_object) - civ_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - return patches - - @ staticmethod - def create_animation( - line: GenieGameEntityGroup, - animation_id: int, - nyan_patch_ref: str, - animation_name: str, - filename_prefix: str - ) -> ForwardRef: - """ - Generates an animation for an ability. - """ - dataset = line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - animation_ref = f"{nyan_patch_ref}.{animation_name}Animation" - animation_obj_name = f"{animation_name}Animation" - animation_raw_api_object = RawAPIObject(animation_ref, animation_obj_name, - dataset.nyan_api_objects) - animation_raw_api_object.add_raw_parent("engine.util.graphics.Animation") - animation_location = ForwardRef(line, nyan_patch_ref) - animation_raw_api_object.set_location(animation_location) - - if animation_id in dataset.combined_sprites.keys(): - animation_sprite = dataset.combined_sprites[animation_id] - - else: - animation_filename = f"{filename_prefix}{name_lookup_dict[line.get_head_unit_id()][1]}" - animation_sprite = CombinedSprite(animation_id, - animation_filename, - dataset) - dataset.combined_sprites.update({animation_sprite.get_id(): animation_sprite}) - - animation_sprite.add_reference(animation_raw_api_object) - - animation_raw_api_object.add_raw_member("sprite", animation_sprite, - "engine.util.graphics.Animation") - - line.add_raw_api_object(animation_raw_api_object) - - animation_forward_ref = ForwardRef(line, animation_ref) + setup_civ_bonus = staticmethod(setup_civ_bonus) + get_modifiers = staticmethod(get_modifiers) + get_starting_resources = staticmethod(get_starting_resources) + setup_tech_tree = staticmethod(setup_tech_tree) + setup_unique_techs = staticmethod(setup_unique_techs) + setup_unique_units = staticmethod(setup_unique_units) - return animation_forward_ref + create_animation = staticmethod(create_animation) From eb4bf33a8e71c0c8b7358bc2bdd7a8d3b85448c4 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 30 May 2025 11:10:59 +0200 Subject: [PATCH 114/163] convert: Refactor AoCMediaSubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../conversion/aoc/media/CMakeLists.txt | 6 + .../conversion/aoc/media/__init__.py | 5 + .../processor/conversion/aoc/media/blend.py | 32 +++ .../conversion/aoc/media/graphics.py | 157 ++++++++++++++ .../processor/conversion/aoc/media/sound.py | 38 ++++ .../conversion/aoc/media_subprocessor.py | 192 +----------------- 7 files changed, 248 insertions(+), 183 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/media/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/media/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/media/blend.py create mode 100644 openage/convert/processor/conversion/aoc/media/graphics.py create mode 100644 openage/convert/processor/conversion/aoc/media/sound.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index 0c98c461b6..ebc18829bc 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -21,4 +21,5 @@ add_subdirectory(ability) add_subdirectory(auxiliary) add_subdirectory(civ) add_subdirectory(effect) +add_subdirectory(media) add_subdirectory(resistance) diff --git a/openage/convert/processor/conversion/aoc/media/CMakeLists.txt b/openage/convert/processor/conversion/aoc/media/CMakeLists.txt new file mode 100644 index 0000000000..e96d374bd8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/media/CMakeLists.txt @@ -0,0 +1,6 @@ +add_py_modules( + __init__.py + blend.py + graphics.py + sound.py +) diff --git a/openage/convert/processor/conversion/aoc/media/__init__.py b/openage/convert/processor/conversion/aoc/media/__init__.py new file mode 100644 index 0000000000..e35ddcd7fa --- /dev/null +++ b/openage/convert/processor/conversion/aoc/media/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create media export requests for media files in the AoC data. +""" diff --git a/openage/convert/processor/conversion/aoc/media/blend.py b/openage/convert/processor/conversion/aoc/media/blend.py new file mode 100644 index 0000000000..fc72393da0 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/media/blend.py @@ -0,0 +1,32 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create export requests for blending files. +""" +from __future__ import annotations +import typing + +from .....entity_object.export.media_export_request import MediaExportRequest +from .....value_object.read.media_types import MediaType + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container\ + import GenieObjectContainer + + +def create_blend_requests(full_data_set: GenieObjectContainer) -> None: + """ + Create export requests for Blendomatic objects. + + TODO: Blendomatic contains multiple files. Better handling? + + :param full_data_set: Data set containing all objects and metadata that the export + requests are added to. + """ + export_request = MediaExportRequest( + MediaType.BLEND, + "data/blend/", + full_data_set.game_version.edition.media_paths[MediaType.BLEND][0], + "blendmode" + ) + full_data_set.blend_exports.update({0: export_request}) diff --git a/openage/convert/processor/conversion/aoc/media/graphics.py b/openage/convert/processor/conversion/aoc/media/graphics.py new file mode 100644 index 0000000000..231d4decc5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/media/graphics.py @@ -0,0 +1,157 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create export requests for graphics files. +""" +from __future__ import annotations +import typing + +from .....entity_object.export.formats.sprite_metadata import LayerMode as SpriteLayerMode +from .....entity_object.export.formats.terrain_metadata import LayerMode as TerrainLayerMode +from .....entity_object.export.media_export_request import MediaExportRequest +from .....entity_object.export.metadata_export import SpriteMetadataExport +from .....entity_object.export.metadata_export import TextureMetadataExport +from .....entity_object.export.metadata_export import TerrainMetadataExport +from .....value_object.read.media_types import MediaType + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container\ + import GenieObjectContainer + + +def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: + """ + Create export requests for graphics referenced by CombinedSprite objects. + + :param full_data_set: Data set containing all objects and metadata that the export + requests are added to. + """ + combined_sprites = full_data_set.combined_sprites.values() + handled_graphic_ids = set() + + for sprite in combined_sprites: + ref_graphics = sprite.get_graphics() + graphic_targetdirs = sprite.resolve_graphics_location() + + # Animation metadata file definiton + sprite_meta_filename = f"{sprite.get_filename()}.sprite" + sprite_meta_export = SpriteMetadataExport(sprite.resolve_sprite_location(), + sprite_meta_filename) + full_data_set.metadata_exports.append(sprite_meta_export) + + for graphic in ref_graphics: + graphic_id = graphic.get_id() + if graphic_id in handled_graphic_ids: + continue + + # Texture image file definiton + targetdir = graphic_targetdirs[graphic_id] + source_filename = f"{str(graphic['slp_id'].value)}.slp" + target_filename = f"{sprite.get_filename()}_{str(graphic['slp_id'].value)}.png" + + export_request = MediaExportRequest(MediaType.GRAPHICS, + targetdir, + source_filename, + target_filename) + full_data_set.graphics_exports.update({graphic_id: export_request}) + + # Texture metadata file definiton + # Same file stem as the image file and same targetdir + texture_meta_filename = f"{target_filename[:-4]}.texture" + texture_meta_export = TextureMetadataExport(targetdir, + texture_meta_filename) + full_data_set.metadata_exports.append(texture_meta_export) + + # Add texture image filename to texture metadata + texture_meta_export.add_imagefile(target_filename) + + # Add metadata from graphics to animation metadata + sequence_type = graphic["sequence_type"].value + if sequence_type == 0x00: + layer_mode = SpriteLayerMode.OFF + + elif sequence_type & 0x08: + layer_mode = SpriteLayerMode.ONCE + + else: + layer_mode = SpriteLayerMode.LOOP + + layer_pos = graphic["layer"].value + frame_rate = round(graphic["frame_rate"].value, ndigits=6) + if frame_rate < 0.000001: + frame_rate = None + + replay_delay = round(graphic["replay_delay"].value, ndigits=6) + if replay_delay < 0.000001: + replay_delay = None + + frame_count = graphic["frame_count"].value + angle_count = graphic["angle_count"].value + mirror_mode = graphic["mirroring_mode"].value + sprite_meta_export.add_graphics_metadata(target_filename, + texture_meta_filename, + layer_mode, + layer_pos, + frame_rate, + replay_delay, + frame_count, + angle_count, + mirror_mode) + + # Notify metadata export about SLP metadata when the file is exported + export_request.add_observer(texture_meta_export) + export_request.add_observer(sprite_meta_export) + + handled_graphic_ids.add(graphic_id) + + combined_terrains = full_data_set.combined_terrains.values() + for texture in combined_terrains: + slp_id = texture.get_terrain()["slp_id"].value + + targetdir = texture.resolve_graphics_location() + source_filename = f"{str(slp_id)}.slp" + target_filename = f"{texture.get_filename()}.png" + + export_request = MediaExportRequest(MediaType.TERRAIN, + targetdir, + source_filename, + target_filename) + full_data_set.graphics_exports.update({slp_id: export_request}) + + texture_meta_filename = f"{texture.get_filename()}.texture" + texture_meta_export = TextureMetadataExport(targetdir, + texture_meta_filename) + full_data_set.metadata_exports.append(texture_meta_export) + + # Add texture image filename to texture metadata + texture_meta_export.add_imagefile(target_filename) + texture_meta_export.update( + None, + { + f"{target_filename}": { + "size": (481, 481), # TODO: Get actual size = sqrt(slp_frame_count) + "subtex_metadata": [ + { + "x": 0, + "y": 0, + "w": 481, + "h": 481, + "cx": 0, + "cy": 0, + } + ] + }} + ) + + terrain_meta_filename = f"{texture.get_filename()}.terrain" + terrain_meta_export = TerrainMetadataExport(targetdir, + terrain_meta_filename) + full_data_set.metadata_exports.append(terrain_meta_export) + + terrain_meta_export.add_graphics_metadata(target_filename, + texture_meta_filename, + TerrainLayerMode.OFF, + 0, + 0.0, + 0.0, + 1) diff --git a/openage/convert/processor/conversion/aoc/media/sound.py b/openage/convert/processor/conversion/aoc/media/sound.py new file mode 100644 index 0000000000..8c9dde5c0d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/media/sound.py @@ -0,0 +1,38 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create export requests for sound files. +""" +from __future__ import annotations +import typing + +from .....entity_object.export.media_export_request import MediaExportRequest +from .....value_object.read.media_types import MediaType + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container\ + import GenieObjectContainer + + +def create_sound_requests(full_data_set: GenieObjectContainer) -> None: + """ + Create export requests for sounds referenced by CombinedSound objects. + + :param full_data_set: Data set containing all objects and metadata that the export + requests are added to. + """ + combined_sounds = full_data_set.combined_sounds.values() + + for sound in combined_sounds: + sound_id = sound.get_file_id() + + targetdir = sound.resolve_sound_location() + source_filename = f"{str(sound_id)}.wav" + target_filename = f"{sound.get_filename()}.opus" + + export_request = MediaExportRequest(MediaType.SOUNDS, + targetdir, + source_filename, + target_filename) + + full_data_set.sound_exports.update({sound_id: export_request}) diff --git a/openage/convert/processor/conversion/aoc/media_subprocessor.py b/openage/convert/processor/conversion/aoc/media_subprocessor.py index 90af7ad297..a207584174 100644 --- a/openage/convert/processor/conversion/aoc/media_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/media_subprocessor.py @@ -1,6 +1,5 @@ -# Copyright 2019-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-few-public-methods,too-many-statements +# Copyright 2019-2025 the openage authors. See copying.md for legal info. + """ Convert media information to metadata definitions and export requests. Subroutine of the main AoC processor. @@ -8,18 +7,12 @@ from __future__ import annotations import typing -from openage.convert.value_object.read.media_types import MediaType - -from ....entity_object.export.formats.sprite_metadata import LayerMode as SpriteLayerMode -from ....entity_object.export.formats.terrain_metadata import LayerMode as TerrainLayerMode -from ....entity_object.export.media_export_request import MediaExportRequest -from ....entity_object.export.metadata_export import SpriteMetadataExport -from ....entity_object.export.metadata_export import TextureMetadataExport -from ....entity_object.export.metadata_export import TerrainMetadataExport -from ....value_object.read.media_types import MediaType +from .media.blend import create_blend_requests +from .media.graphics import create_graphics_requests +from .media.sound import create_sound_requests if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_object_container\ + from ....entity_object.conversion.aoc.genie_object_container\ import GenieObjectContainer @@ -37,173 +30,6 @@ def convert(cls, full_data_set: GenieObjectContainer) -> None: # cls.create_blend_requests(full_data_set) cls.create_sound_requests(full_data_set) - @staticmethod - def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: - """ - Create export requests for graphics referenced by CombinedSprite objects. - """ - combined_sprites = full_data_set.combined_sprites.values() - handled_graphic_ids = set() - - for sprite in combined_sprites: - ref_graphics = sprite.get_graphics() - graphic_targetdirs = sprite.resolve_graphics_location() - - # Animation metadata file definiton - sprite_meta_filename = f"{sprite.get_filename()}.sprite" - sprite_meta_export = SpriteMetadataExport(sprite.resolve_sprite_location(), - sprite_meta_filename) - full_data_set.metadata_exports.append(sprite_meta_export) - - for graphic in ref_graphics: - graphic_id = graphic.get_id() - if graphic_id in handled_graphic_ids: - continue - - # Texture image file definiton - targetdir = graphic_targetdirs[graphic_id] - source_filename = f"{str(graphic['slp_id'].value)}.slp" - target_filename = f"{sprite.get_filename()}_{str(graphic['slp_id'].value)}.png" - - export_request = MediaExportRequest(MediaType.GRAPHICS, - targetdir, - source_filename, - target_filename) - full_data_set.graphics_exports.update({graphic_id: export_request}) - - # Texture metadata file definiton - # Same file stem as the image file and same targetdir - texture_meta_filename = f"{target_filename[:-4]}.texture" - texture_meta_export = TextureMetadataExport(targetdir, - texture_meta_filename) - full_data_set.metadata_exports.append(texture_meta_export) - - # Add texture image filename to texture metadata - texture_meta_export.add_imagefile(target_filename) - - # Add metadata from graphics to animation metadata - sequence_type = graphic["sequence_type"].value - if sequence_type == 0x00: - layer_mode = SpriteLayerMode.OFF - - elif sequence_type & 0x08: - layer_mode = SpriteLayerMode.ONCE - - else: - layer_mode = SpriteLayerMode.LOOP - - layer_pos = graphic["layer"].value - frame_rate = round(graphic["frame_rate"].value, ndigits=6) - if frame_rate < 0.000001: - frame_rate = None - - replay_delay = round(graphic["replay_delay"].value, ndigits=6) - if replay_delay < 0.000001: - replay_delay = None - - frame_count = graphic["frame_count"].value - angle_count = graphic["angle_count"].value - mirror_mode = graphic["mirroring_mode"].value - sprite_meta_export.add_graphics_metadata(target_filename, - texture_meta_filename, - layer_mode, - layer_pos, - frame_rate, - replay_delay, - frame_count, - angle_count, - mirror_mode) - - # Notify metadata export about SLP metadata when the file is exported - export_request.add_observer(texture_meta_export) - export_request.add_observer(sprite_meta_export) - - handled_graphic_ids.add(graphic_id) - - combined_terrains = full_data_set.combined_terrains.values() - for texture in combined_terrains: - slp_id = texture.get_terrain()["slp_id"].value - - targetdir = texture.resolve_graphics_location() - source_filename = f"{str(slp_id)}.slp" - target_filename = f"{texture.get_filename()}.png" - - export_request = MediaExportRequest(MediaType.TERRAIN, - targetdir, - source_filename, - target_filename) - full_data_set.graphics_exports.update({slp_id: export_request}) - - texture_meta_filename = f"{texture.get_filename()}.texture" - texture_meta_export = TextureMetadataExport(targetdir, - texture_meta_filename) - full_data_set.metadata_exports.append(texture_meta_export) - - # Add texture image filename to texture metadata - texture_meta_export.add_imagefile(target_filename) - texture_meta_export.update( - None, - { - f"{target_filename}": { - "size": (481, 481), # TODO: Get actual size = sqrt(slp_frame_count) - "subtex_metadata": [ - { - "x": 0, - "y": 0, - "w": 481, - "h": 481, - "cx": 0, - "cy": 0, - } - ] - }} - ) - - terrain_meta_filename = f"{texture.get_filename()}.terrain" - terrain_meta_export = TerrainMetadataExport(targetdir, - terrain_meta_filename) - full_data_set.metadata_exports.append(terrain_meta_export) - - terrain_meta_export.add_graphics_metadata(target_filename, - texture_meta_filename, - TerrainLayerMode.OFF, - 0, - 0.0, - 0.0, - 1) - - @staticmethod - def create_blend_requests(full_data_set: GenieObjectContainer) -> None: - """ - Create export requests for Blendomatic objects. - - TODO: Blendomatic contains multiple files. Better handling? - """ - export_request = MediaExportRequest( - MediaType.BLEND, - "data/blend/", - full_data_set.game_version.edition.media_paths[MediaType.BLEND][0], - "blendmode" - ) - full_data_set.blend_exports.update({0: export_request}) - - @staticmethod - def create_sound_requests(full_data_set: GenieObjectContainer) -> None: - """ - Create export requests for sounds referenced by CombinedSound objects. - """ - combined_sounds = full_data_set.combined_sounds.values() - - for sound in combined_sounds: - sound_id = sound.get_file_id() - - targetdir = sound.resolve_sound_location() - source_filename = f"{str(sound_id)}.wav" - target_filename = f"{sound.get_filename()}.opus" - - export_request = MediaExportRequest(MediaType.SOUNDS, - targetdir, - source_filename, - target_filename) - - full_data_set.sound_exports.update({sound_id: export_request}) + create_blend_requests = staticmethod(create_blend_requests) + create_graphics_requests = staticmethod(create_graphics_requests) + create_sound_requests = staticmethod(create_sound_requests) From 326cdfdde3af7730cf05557bc208687db21c7655 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 30 May 2025 11:31:20 +0200 Subject: [PATCH 115/163] convert: Refactor AoCModifierSubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../conversion/aoc/ability/__init__.py | 3 +- .../conversion/aoc/modifier/CMakeLists.txt | 7 + .../conversion/aoc/modifier/__init__.py | 5 + .../aoc/modifier/elevation_attack.py | 31 +++ .../conversion/aoc/modifier/flyover_effect.py | 26 +++ .../conversion/aoc/modifier/gather_rate.py | 122 ++++++++++ .../conversion/aoc/modifier/move_speed.py | 49 ++++ .../conversion/aoc/modifier_subprocessor.py | 213 +----------------- 9 files changed, 252 insertions(+), 205 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/modifier/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/modifier/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/modifier/elevation_attack.py create mode 100644 openage/convert/processor/conversion/aoc/modifier/flyover_effect.py create mode 100644 openage/convert/processor/conversion/aoc/modifier/gather_rate.py create mode 100644 openage/convert/processor/conversion/aoc/modifier/move_speed.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index ebc18829bc..fd728e254a 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -22,4 +22,5 @@ add_subdirectory(auxiliary) add_subdirectory(civ) add_subdirectory(effect) add_subdirectory(media) +add_subdirectory(modifier) add_subdirectory(resistance) diff --git a/openage/convert/processor/conversion/aoc/ability/__init__.py b/openage/convert/processor/conversion/aoc/ability/__init__.py index 93261ab007..614701233a 100644 --- a/openage/convert/processor/conversion/aoc/ability/__init__.py +++ b/openage/convert/processor/conversion/aoc/ability/__init__.py @@ -1,6 +1,5 @@ # Copyright 2025-2025 the openage authors. See copying.md for legal info. """ -Derives and adds abilities to game entities created from lines. Subroutine of the -nyan subprocessor. +Derives and adds abilities to game entities created from lines. """ diff --git a/openage/convert/processor/conversion/aoc/modifier/CMakeLists.txt b/openage/convert/processor/conversion/aoc/modifier/CMakeLists.txt new file mode 100644 index 0000000000..7cbc48885b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/modifier/CMakeLists.txt @@ -0,0 +1,7 @@ +add_py_modules( + __init__.py + elevation_attack.py + flyover_effect.py + gather_rate.py + move_speed.py +) diff --git a/openage/convert/processor/conversion/aoc/modifier/__init__.py b/openage/convert/processor/conversion/aoc/modifier/__init__.py new file mode 100644 index 0000000000..d0cb6594ae --- /dev/null +++ b/openage/convert/processor/conversion/aoc/modifier/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Derives and adds modifiers to lines or civ groups. +""" diff --git a/openage/convert/processor/conversion/aoc/modifier/elevation_attack.py b/openage/convert/processor/conversion/aoc/modifier/elevation_attack.py new file mode 100644 index 0000000000..66de845647 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/modifier/elevation_attack.py @@ -0,0 +1,31 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ElevationAttack modifier. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import NyanObject + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def elevation_attack_modifiers(converter_obj_group: GenieGameEntityGroup) -> list[NyanObject]: + """ + Adds the pregenerated elevation damage multipliers to a line or civ group. + + :param converter_obj_group: ConverterObjectGroup that gets the modifier. + :returns: The forward references for the modifier. + """ + dataset = converter_obj_group.data + modifiers = [ + dataset.pregen_nyan_objects[ + "util.modifier.elevation_difference.AttackHigh" + ].get_nyan_object(), + dataset.pregen_nyan_objects[ + "util.modifier.elevation_difference.AttackLow" + ].get_nyan_object() + ] + + return modifiers diff --git a/openage/convert/processor/conversion/aoc/modifier/flyover_effect.py b/openage/convert/processor/conversion/aoc/modifier/flyover_effect.py new file mode 100644 index 0000000000..9f5b5ecd6c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/modifier/flyover_effect.py @@ -0,0 +1,26 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the FlyoverEffect modifier. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import NyanObject + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def flyover_effect_modifier(converter_obj_group: GenieGameEntityGroup) -> NyanObject: + """ + Adds the pregenerated fly-over-cliff damage multiplier to a line or civ group. + + :param converter_obj_group: ConverterObjectGroup that gets the modifier. + :returns: The forward reference for the modifier. + """ + dataset = converter_obj_group.data + modifier = dataset.pregen_nyan_objects[ + "util.modifier.flyover_cliff.AttackFlyover" + ].get_nyan_object() + + return modifier diff --git a/openage/convert/processor/conversion/aoc/modifier/gather_rate.py b/openage/convert/processor/conversion/aoc/modifier/gather_rate.py new file mode 100644 index 0000000000..7d88f19086 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/modifier/gather_rate.py @@ -0,0 +1,122 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the GatherRate modifier. +""" + +from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup, \ + GenieBuildingLineGroup, GenieVillagerGroup, GenieAmbientGroup, GenieVariantGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + + +def gather_rate_modifier(converter_obj_group: GenieGameEntityGroup) -> list[ForwardRef]: + """ + Adds Gather modifiers to a line or civ group. + + :param converter_obj_group: ConverterObjectGroup that gets the modifier. + :returns: The forward reference for the modifier. + """ + dataset = converter_obj_group.data + + modifiers = [] + + if isinstance(converter_obj_group, GenieGameEntityGroup): + if isinstance(converter_obj_group, GenieVillagerGroup): + gatherers = converter_obj_group.variants[0].line + + else: + gatherers = [converter_obj_group.line[0]] + + head_unit_id = converter_obj_group.get_head_unit_id() + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + target_obj_name = name_lookup_dict[head_unit_id][0] + + for gatherer in gatherers: + unit_commands = gatherer["unit_commands"].value + + for command in unit_commands: + # Find a gather ability. + type_id = command["type"].value + + gather_task_ids = internal_name_lookups.get_gather_lookups( + dataset.game_version).keys() + if type_id not in gather_task_ids: + continue + + work_value = command["work_value1"].value + + # Check if the work value is 1 (with some rounding error margin) + # if not, we have to create a modifier + if 0.9999 < work_value < 1.0001: + continue + + # Search for the lines with the matching class/unit id + class_id = command["class_id"].value + unit_id = command["unit_id"].value + + entity_lines = {} + entity_lines.update(dataset.unit_lines) + entity_lines.update(dataset.building_lines) + entity_lines.update(dataset.ambient_groups) + entity_lines.update(dataset.variant_groups) + + if unit_id != -1: + lines = [entity_lines[unit_id]] + + elif class_id != -1: + lines = [] + for line in entity_lines.values(): + if line.get_class_id() == class_id: + lines.append(line) + + else: + raise ValueError("Gather task has no valid target ID.") + + # Create a modifier for each matching resource spot + for resource_line in lines: + head_unit_id = resource_line.get_head_unit_id() + if isinstance(resource_line, GenieBuildingLineGroup): + resource_line_name = name_lookup_dict[head_unit_id][0] + + elif isinstance(resource_line, GenieAmbientGroup): + resource_line_name = name_lookup_dict[head_unit_id][0] + + elif isinstance(resource_line, GenieVariantGroup): + resource_line_name = name_lookup_dict[head_unit_id][1] + + modifier_ref = f"{target_obj_name}.{resource_line_name}GatheringRate" + modifier_raw_api_object = RawAPIObject(modifier_ref, + "%sGatheringRate", + dataset.nyan_api_objects) + modifier_raw_api_object.add_raw_parent( + "engine.modifier.multiplier.type.GatheringRate") + modifier_location = ForwardRef(converter_obj_group, target_obj_name) + modifier_raw_api_object.set_location(modifier_location) + + # Multiplier + modifier_raw_api_object.add_raw_member( + "multiplier", + work_value, + "engine.modifier.multiplier.MultiplierModifier" + ) + + # Resource spot + spot_ref = (f"{resource_line_name}.Harvestable." + f"{resource_line_name}ResourceSpot") + spot_forward_ref = ForwardRef(resource_line, spot_ref) + modifier_raw_api_object.add_raw_member( + "resource_spot", + spot_forward_ref, + "engine.modifier.multiplier.type.GatheringRate" + ) + + converter_obj_group.add_raw_api_object(modifier_raw_api_object) + modifier_forward_ref = ForwardRef(converter_obj_group, + modifier_raw_api_object.get_id()) + modifiers.append(modifier_forward_ref) + + return modifiers diff --git a/openage/convert/processor/conversion/aoc/modifier/move_speed.py b/openage/convert/processor/conversion/aoc/modifier/move_speed.py new file mode 100644 index 0000000000..8c13a933bb --- /dev/null +++ b/openage/convert/processor/conversion/aoc/modifier/move_speed.py @@ -0,0 +1,49 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the MoveSpeed modifier. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def move_speed_modifier(converter_obj_group: GenieGameEntityGroup, value: float) -> ForwardRef: + """ + Adds a MoveSpeed modifier to a line or civ group. + + :param converter_obj_group: ConverterObjectGroup that gets the modifier. + :returns: The forward reference for the modifier. + """ + dataset = converter_obj_group.data + if isinstance(converter_obj_group, GenieGameEntityGroup): + head_unit_id = converter_obj_group.get_head_unit_id() + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + target_obj_name = name_lookup_dict[head_unit_id][0] + + else: + # Civs + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + target_obj_name = civ_lookup_dict[converter_obj_group.get_id()][0] + + modifier_ref = f"{target_obj_name}.MoveSpeed" + modifier_raw_api_object = RawAPIObject(modifier_ref, "MoveSpeed", dataset.nyan_api_objects) + modifier_raw_api_object.add_raw_parent("engine.modifier.multiplier.type.MoveSpeed") + modifier_location = ForwardRef(converter_obj_group, target_obj_name) + modifier_raw_api_object.set_location(modifier_location) + + modifier_raw_api_object.add_raw_member("multiplier", + value, + "engine.modifier.multiplier.MultiplierModifier") + + converter_obj_group.add_raw_api_object(modifier_raw_api_object) + + modifier_forward_ref = ForwardRef(converter_obj_group, modifier_raw_api_object.get_id()) + + return modifier_forward_ref diff --git a/openage/convert/processor/conversion/aoc/modifier_subprocessor.py b/openage/convert/processor/conversion/aoc/modifier_subprocessor.py index 01f095145d..dae79d9d79 100644 --- a/openage/convert/processor/conversion/aoc/modifier_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/modifier_subprocessor.py @@ -1,23 +1,14 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-branches,too-many-nested-blocks,too-many-statements +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ -Derives and adds abilities to lines or civ groups. Subroutine of the +Derives and adds modifiers to lines or civ groups. Subroutine of the nyan subprocessor. """ -from __future__ import annotations -import typing -from ....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup, \ - GenieBuildingLineGroup, GenieVillagerGroup, GenieAmbientGroup, \ - GenieVariantGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.nyan.nyan_structs import NyanObject +from .modifier.elevation_attack import elevation_attack_modifiers +from .modifier.flyover_effect import flyover_effect_modifier +from .modifier.gather_rate import gather_rate_modifier +from .modifier.move_speed import move_speed_modifier class AoCModifierSubprocessor: @@ -25,191 +16,7 @@ class AoCModifierSubprocessor: Creates raw API objects for modifiers in AoC. """ - @staticmethod - def elevation_attack_modifiers(converter_obj_group: GenieGameEntityGroup) -> list[NyanObject]: - """ - Adds the pregenerated elevation damage multipliers to a line or civ group. - - :param converter_obj_group: ConverterObjectGroup that gets the modifier. - :type converter_obj_group: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward references for the modifier. - :rtype: list - """ - dataset = converter_obj_group.data - modifiers = [ - dataset.pregen_nyan_objects[ - "util.modifier.elevation_difference.AttackHigh" - ].get_nyan_object(), - dataset.pregen_nyan_objects[ - "util.modifier.elevation_difference.AttackLow" - ].get_nyan_object() - ] - - return modifiers - - @staticmethod - def flyover_effect_modifier(converter_obj_group: GenieGameEntityGroup) -> NyanObject: - """ - Adds the pregenerated fly-over-cliff damage multiplier to a line or civ group. - - :param converter_obj_group: ConverterObjectGroup that gets the modifier. - :type converter_obj_group: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward reference for the modifier. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - dataset = converter_obj_group.data - modifier = dataset.pregen_nyan_objects[ - "util.modifier.flyover_cliff.AttackFlyover" - ].get_nyan_object() - - return modifier - - @staticmethod - def gather_rate_modifier(converter_obj_group: GenieGameEntityGroup) -> list[ForwardRef]: - """ - Adds Gather modifiers to a line or civ group. - - :param converter_obj_group: ConverterObjectGroup that gets the modifier. - :type converter_obj_group: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward reference for the modifier. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - dataset = converter_obj_group.data - - modifiers = [] - - if isinstance(converter_obj_group, GenieGameEntityGroup): - if isinstance(converter_obj_group, GenieVillagerGroup): - gatherers = converter_obj_group.variants[0].line - - else: - gatherers = [converter_obj_group.line[0]] - - head_unit_id = converter_obj_group.get_head_unit_id() - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - target_obj_name = name_lookup_dict[head_unit_id][0] - - for gatherer in gatherers: - unit_commands = gatherer["unit_commands"].value - - for command in unit_commands: - # Find a gather ability. - type_id = command["type"].value - - gather_task_ids = internal_name_lookups.get_gather_lookups( - dataset.game_version).keys() - if type_id not in gather_task_ids: - continue - - work_value = command["work_value1"].value - - # Check if the work value is 1 (with some rounding error margin) - # if not, we have to create a modifier - if 0.9999 < work_value < 1.0001: - continue - - # Search for the lines with the matching class/unit id - class_id = command["class_id"].value - unit_id = command["unit_id"].value - - entity_lines = {} - entity_lines.update(dataset.unit_lines) - entity_lines.update(dataset.building_lines) - entity_lines.update(dataset.ambient_groups) - entity_lines.update(dataset.variant_groups) - - if unit_id != -1: - lines = [entity_lines[unit_id]] - - elif class_id != -1: - lines = [] - for line in entity_lines.values(): - if line.get_class_id() == class_id: - lines.append(line) - - else: - raise ValueError("Gather task has no valid target ID.") - - # Create a modifier for each matching resource spot - for resource_line in lines: - head_unit_id = resource_line.get_head_unit_id() - if isinstance(resource_line, GenieBuildingLineGroup): - resource_line_name = name_lookup_dict[head_unit_id][0] - - elif isinstance(resource_line, GenieAmbientGroup): - resource_line_name = name_lookup_dict[head_unit_id][0] - - elif isinstance(resource_line, GenieVariantGroup): - resource_line_name = name_lookup_dict[head_unit_id][1] - - modifier_ref = f"{target_obj_name}.{resource_line_name}GatheringRate" - modifier_raw_api_object = RawAPIObject(modifier_ref, - "%sGatheringRate", - dataset.nyan_api_objects) - modifier_raw_api_object.add_raw_parent( - "engine.modifier.multiplier.type.GatheringRate") - modifier_location = ForwardRef(converter_obj_group, target_obj_name) - modifier_raw_api_object.set_location(modifier_location) - - # Multiplier - modifier_raw_api_object.add_raw_member( - "multiplier", - work_value, - "engine.modifier.multiplier.MultiplierModifier" - ) - - # Resource spot - spot_ref = (f"{resource_line_name}.Harvestable." - f"{resource_line_name}ResourceSpot") - spot_forward_ref = ForwardRef(resource_line, spot_ref) - modifier_raw_api_object.add_raw_member( - "resource_spot", - spot_forward_ref, - "engine.modifier.multiplier.type.GatheringRate" - ) - - converter_obj_group.add_raw_api_object(modifier_raw_api_object) - modifier_forward_ref = ForwardRef(converter_obj_group, - modifier_raw_api_object.get_id()) - modifiers.append(modifier_forward_ref) - - return modifiers - - @staticmethod - def move_speed_modifier(converter_obj_group: GenieGameEntityGroup, value: float) -> ForwardRef: - """ - Adds a MoveSpeed modifier to a line or civ group. - - :param converter_obj_group: ConverterObjectGroup that gets the modifier. - :type converter_obj_group: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward reference for the modifier. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - dataset = converter_obj_group.data - if isinstance(converter_obj_group, GenieGameEntityGroup): - head_unit_id = converter_obj_group.get_head_unit_id() - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - target_obj_name = name_lookup_dict[head_unit_id][0] - - else: - # Civs - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - target_obj_name = civ_lookup_dict[converter_obj_group.get_id()][0] - - modifier_ref = f"{target_obj_name}.MoveSpeed" - modifier_raw_api_object = RawAPIObject(modifier_ref, "MoveSpeed", dataset.nyan_api_objects) - modifier_raw_api_object.add_raw_parent("engine.modifier.multiplier.type.MoveSpeed") - modifier_location = ForwardRef(converter_obj_group, target_obj_name) - modifier_raw_api_object.set_location(modifier_location) - - modifier_raw_api_object.add_raw_member("multiplier", - value, - "engine.modifier.multiplier.MultiplierModifier") - - converter_obj_group.add_raw_api_object(modifier_raw_api_object) - - modifier_forward_ref = ForwardRef(converter_obj_group, modifier_raw_api_object.get_id()) - - return modifier_forward_ref + elevation_attack_modifiers = staticmethod(elevation_attack_modifiers) + flyover_effect_modifier = staticmethod(flyover_effect_modifier) + gather_rate_modifier = staticmethod(gather_rate_modifier) + move_speed_modifier = staticmethod(move_speed_modifier) From c668003e082cc55fa065774041ce9bbe18fdd482 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 30 May 2025 12:13:01 +0200 Subject: [PATCH 116/163] convert: Refactor AoCModpackSubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../conversion/aoc/modpack/CMakeLists.txt | 4 + .../conversion/aoc/modpack/__init__.py | 5 + .../conversion/aoc/modpack/import_tree.py | 432 ++++++++++++++++++ .../conversion/aoc/modpack_subprocessor.py | 327 +------------ 5 files changed, 446 insertions(+), 323 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/modpack/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/modpack/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/modpack/import_tree.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index fd728e254a..23d68048cc 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -23,4 +23,5 @@ add_subdirectory(civ) add_subdirectory(effect) add_subdirectory(media) add_subdirectory(modifier) +add_subdirectory(modpack) add_subdirectory(resistance) diff --git a/openage/convert/processor/conversion/aoc/modpack/CMakeLists.txt b/openage/convert/processor/conversion/aoc/modpack/CMakeLists.txt new file mode 100644 index 0000000000..b2de41b89c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/modpack/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + import_tree.py +) diff --git a/openage/convert/processor/conversion/aoc/modpack/__init__.py b/openage/convert/processor/conversion/aoc/modpack/__init__.py new file mode 100644 index 0000000000..e85cf69fd6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/modpack/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Organize export data (nyan objects, media, scripts, etc.) into modpacks. +""" diff --git a/openage/convert/processor/conversion/aoc/modpack/import_tree.py b/openage/convert/processor/conversion/aoc/modpack/import_tree.py new file mode 100644 index 0000000000..6065b15cf1 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/modpack/import_tree.py @@ -0,0 +1,432 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create the aliases for the nyan import tree. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.import_tree import ImportTree + + +def set_static_aliases(modpack_name: str, import_tree: ImportTree) -> None: + """ + Set the aliases for the nyan import tree. The alias names + and affected nodes are hardcoded in this function. + + :param modpack: Modpack that is being converted. + :param import_tree: The import tree to set the aliases for. + """ + # Prefix aliases to prevent naming conflicts + prefix = modpack_name + "_" + + # Aliases for objects from the openage API + _add_ability_aliases(import_tree) + _add_utility_aliases(import_tree) + _add_effect_aliases(import_tree) + _add_modifier_aliases(import_tree) + + # Aliases for objects from the modpack itself + # TODO: Make this less hacky + _add_modpack_utility_aliases(modpack_name, prefix, import_tree) + _add_modpack_effect_aliases(modpack_name, import_tree) + _add_modpack_modifier_aliases(modpack_name, prefix, import_tree) + _add_modpack_terrain_aliases(modpack_name, prefix, import_tree) + _add_modpack_game_entity_aliases(modpack_name, import_tree) + _add_modpack_tech_aliases(modpack_name, import_tree) + _add_modpack_civ_aliases(modpack_name, import_tree) + + +def _add_ability_aliases(import_tree: ImportTree) -> None: + """ + Add aliases for abilities from the openage API. + + :param import_tree: The import tree to set the aliases for. + """ + # Abilities from the openage API + import_tree.add_alias(("engine", "ability", "type"), "ability") + import_tree.add_alias(("engine", "ability", "property", "type"), "ability_prop") + + +def _add_utility_aliases(import_tree: ImportTree) -> None: + """ + Add aliases for utility objects from the openage API. + + :param import_tree: The import tree to set the aliases for. + """ + import_tree.add_alias( + ("engine", "util", "activity", "condition", "type"), "activity_condition" + ) + import_tree.add_alias(("engine", "util", "activity", "event", "type"), "activity_event") + import_tree.add_alias(("engine", "util", "activity", "node", "type"), "activity_node") + import_tree.add_alias(("engine", "util", "accuracy"), "accuracy") + import_tree.add_alias( + ("engine", "util", "animation_override"), "animation_override" + ) + import_tree.add_alias(("engine", "util", "attribute"), "attribute") + import_tree.add_alias(("engine", "util", "attribute_change_type", "type"), + "attribute_change_type") + import_tree.add_alias(("engine", "util", "calculation_type", "type"), "calculation_type") + import_tree.add_alias(("engine", "util", "setup"), "civ") + import_tree.add_alias(("engine", "util", "convert_type"), "convert_type") + import_tree.add_alias(("engine", "util", "cost", "type"), "cost_type") + import_tree.add_alias(("engine", "util", "create"), "create") + import_tree.add_alias(("engine", "util", "diplomatic_stance"), "diplo_stance") + import_tree.add_alias( + ("engine", "util", "diplomatic_stance", "type"), + "diplo_stance_type" + ) + import_tree.add_alias(("engine", "util", "distribution_type", "type"), "distribution_type") + import_tree.add_alias(("engine", "util", "dropoff_type", "type"), "dropoff_type") + import_tree.add_alias(("engine", "util", "exchange_mode", "type"), "exchange_mode") + import_tree.add_alias(("engine", "util", "exchange_rate"), "exchange_rate") + import_tree.add_alias(("engine", "util", "formation"), "formation") + import_tree.add_alias(("engine", "util", "game_entity"), "game_entity") + import_tree.add_alias( + ("engine", "util", "game_entity_formation"), "ge_formation" + ) + import_tree.add_alias(("engine", "util", "game_entity_stance", "type"), "ge_stance") + import_tree.add_alias(("engine", "util", "game_entity_type", "type"), "ge_type") + import_tree.add_alias( + ("engine", "util", "game_entity_type"), "game_entity_type" + ) + import_tree.add_alias(("engine", "util", "graphics"), "graphics") + import_tree.add_alias(("engine", "util", "herdable_mode", "type"), "herdable_mode") + import_tree.add_alias(("engine", "util", "hitbox"), "hitbox") + import_tree.add_alias(("engine", "util", "move_mode", "type"), "move_mode") + import_tree.add_alias(("engine", "util", "language"), "lang") + import_tree.add_alias(("engine", "util", "language", "translated", "type"), "translated") + import_tree.add_alias(("engine", "util", "logic", "gate", "type"), "logic_gate") + import_tree.add_alias(("engine", "util", "logic", "literal", "type"), "literal") + import_tree.add_alias(("engine", "util", "logic", "literal_scope", "type"), "literal_scope") + import_tree.add_alias(("engine", "util", "patch"), "patch") + import_tree.add_alias(("engine", "util", "patch", "property", "type"), "patch_prop") + import_tree.add_alias(("engine", "util", "path_type"), "path_type") + import_tree.add_alias(("engine", "util", "payment_mode", "type"), "payment_mode") + import_tree.add_alias(("engine", "util", "placement_mode", "type"), "placement_mode") + import_tree.add_alias(("engine", "util", "price_mode", "type"), "price_mode") + import_tree.add_alias(("engine", "util", "price_pool"), "price_pool") + import_tree.add_alias(("engine", "util", "production_mode", "type"), "production_mode") + import_tree.add_alias(("engine", "util", "progress"), "progress") + import_tree.add_alias(("engine", "util", "progress", "property", "type"), "progress_prop") + import_tree.add_alias(("engine", "util", "progress_status"), "progress_status") + import_tree.add_alias(("engine", "util", "progress_type", "type"), "progress_type") + import_tree.add_alias(("engine", "util", "research"), "research") + import_tree.add_alias(("engine", "util", "resource"), "resource") + import_tree.add_alias(("engine", "util", "resource_spot"), "resource_spot") + import_tree.add_alias(("engine", "util", "selection_box", "type"), "selection_box") + import_tree.add_alias(("engine", "util", "sound",), "sound") + import_tree.add_alias(("engine", "util", "state_machine"), "state_machine") + import_tree.add_alias(("engine", "util", "storage"), "storage") + import_tree.add_alias(("engine", "util", "target_mode", "type"), "target_mode") + import_tree.add_alias(("engine", "util", "tech"), "tech") + import_tree.add_alias(("engine", "util", "terrain"), "terrain") + import_tree.add_alias(("engine", "util", "terrain_type"), "terrain_type") + import_tree.add_alias(("engine", "util", "trade_route", "type"), "trade_route") + import_tree.add_alias(("engine", "util", "variant", "type"), "variant") + + +def _add_effect_aliases(import_tree: ImportTree) -> None: + """ + Add aliases for effect objects from the openage API. + + :param import_tree: The import tree to set the aliases for. + """ + import_tree.add_alias(("engine", "effect", "property", "type"), "effect_prop") + import_tree.add_alias( + ("engine", "effect", "continuous", "flat_attribute_change", "type"), + "econt_flac" + ) + import_tree.add_alias( + ("engine", "effect", "continuous", "time_relative_progress", "type"), + "econt_trp" + ) + import_tree.add_alias( + ("engine", "effect", "continuous", "time_relative_attribute", "type"), + "econt_tra" + ) + import_tree.add_alias( + ("engine", "effect", "discrete", "convert", "type"), + "edisc_conv" + ) + import_tree.add_alias( + ("engine", "effect", "discrete", "flat_attribute_change", "type"), + "edisc_flac" + ) + import_tree.add_alias(("engine", "resistance", "property", "type"), "resist_prop") + import_tree.add_alias( + ("engine", "resistance", "continuous", "flat_attribute_change", "type"), + "rcont_flac" + ) + import_tree.add_alias( + ("engine", "resistance", "continuous", "time_relative_progress", "type"), + "rcont_trp" + ) + import_tree.add_alias( + ("engine", "resistance", "continuous", "time_relative_attribute", "type"), + "rcont_tra" + ) + import_tree.add_alias( + ("engine", "resistance", "discrete", "convert", "type"), + "rdisc_conv" + ) + import_tree.add_alias( + ("engine", "resistance", "discrete", "flat_attribute_change", "type"), + "rdisc_flac" + ) + + +def _add_modifier_aliases(import_tree: ImportTree) -> None: + """ + Add aliases for modifier objects from the openage API. + + :param import_tree: The import tree to set the aliases for. + """ + import_tree.add_alias( + ("engine", "modifier", "effect", "flat_attribute_change", "type"), + "me_flac" + ) + + +def _add_modpack_utility_aliases(modpack_name: str, prefix: str, import_tree: ImportTree) -> None: + """ + Add aliases for utility objects from the modpack. + + :param modpack_name: The name of the modpack. + :param prefix: The prefix to use for the aliases to prevent conflicts with the + openage API namespace 'engine'. + :param import_tree: The import tree to set the aliases for. + """ + import_tree.add_alias( + (modpack_name, "data", "util", "attribute", "types"), + prefix + "attribute" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "attribute_change_type", "types"), + prefix + "attr_change_type" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "construct_type", "types"), + prefix + "construct_type" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "convert_type", "types"), + prefix + "convert_type" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "diplomatic_stance", "types"), + prefix + "diplo_stance" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "game_entity_type", "types"), + prefix + "ge_type" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "formation", "types"), + prefix + "formation" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "formation", "subformations"), + prefix + "subformations" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "language", "language"), + prefix + "lang" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "logic", "death", "death"), + "death_condition" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "logic", "garrison_empty", + "garrison_empty"), + "empty_garrison_condition" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "path_type", "types"), + prefix + "path_type" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "resource", "market_trading"), + prefix + "market_trading" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "resource", "types"), + prefix + "resource" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "terrain_type", "types"), + prefix + "terrain_type" + ) + + +def _add_modpack_effect_aliases(modpack_name: str, import_tree: ImportTree) -> None: + """ + Add aliases for effect objects from the modpack. + + :param modpack_name: The name of the modpack. + :param import_tree: The import tree to set the aliases for. + """ + import_tree.add_alias( + (modpack_name, "data", "effect", "discrete", "flat_attribute_change", + "fallback"), + "attack_fallback" + ) + import_tree.add_alias( + (modpack_name, "data", "effect", "discrete", "flat_attribute_change", + "min_damage"), + "min_damage" + ) + import_tree.add_alias( + (modpack_name, "data", "effect", "discrete", "flat_attribute_change", + "min_heal"), + "min_heal" + ) + + +def _add_modpack_modifier_aliases(modpack_name: str, prefix: str, import_tree: ImportTree) -> None: + """ + Add aliases for modifier objects from the modpack. + + :param modpack_name: The name of the modpack. + :param prefix: The prefix to use for the aliases to prevent conflicts with the + openage API namespace 'engine'. + :param import_tree: The import tree to set the aliases for. + """ + import_tree.add_alias( + (modpack_name, "data", "util", "modifier", "elevation_difference", + "elevation_difference"), + prefix + "mme_elev_high" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "modifier", "elevation_difference", + "elevation_difference"), + prefix + "mme_elev_low" + ) + import_tree.add_alias( + (modpack_name, "data", "util", "modifier", "flyover_cliff", + "flyover_cliff"), + prefix + "mme_cliff_attack" + ) + + +def _add_modpack_terrain_aliases(modpack_name: str, prefix: str, import_tree: ImportTree) -> None: + """ + Add aliases for terrain objects from the modpack. + + :param modpack_name: The name of the modpack. + :param prefix: The prefix to use for the aliases to prevent conflicts with the + openage API namespace 'engine'. + :param import_tree: The import tree to set the aliases for. + """ + import_tree.add_alias( + (modpack_name, "data", "terrain", "foundation", "foundation"), + prefix + "foundation" + ) + + # Modpack terrain objects + fqon = (modpack_name, "data", "terrain") + current_node = import_tree.root + for part in fqon: + current_node = current_node.get_child(part) + + for child in current_node.children.values(): + current_node = child + + # These are folders and should have unique names + alias_name = "terrain_" + current_node.name + + # One level deeper: This should be the nyan file + current_node = current_node.children[current_node.name] + + # Set the folder name as alias for the file + current_node.set_alias(alias_name) + + +def _add_modpack_game_entity_aliases(modpack_name: str, import_tree: ImportTree) -> None: + """ + Add aliases for game entity objects from the modpack. + + :param modpack_name: The name of the modpack. + :param import_tree: The import tree to set the aliases for. + """ + fqon = (modpack_name, "data", "game_entity", "generic") + current_node = import_tree.root + for part in fqon: + current_node = current_node.get_child(part) + + for child in current_node.children.values(): + current_node = child + + # These are folders and should have unique names + alias_name = f"ge_{current_node.name}" + + for subchild in current_node.children.values(): + if subchild.name in ("graphics", "sounds", "projectiles"): + continue + + if subchild.name.endswith("upgrade"): + alias = f"{alias_name}_{subchild.name}" + subchild.set_alias(alias) + continue + + # One level deeper: This should be the nyan file + current_node = subchild + + alias = f"ge_{current_node.name}" + + # Use the file name as alias for the file + current_node.set_alias(alias) + + +def _add_modpack_tech_aliases(modpack_name: str, import_tree: ImportTree) -> None: + """ + Add aliases for tech objects from the modpack. + + :param modpack_name: The name of the modpack. + :param prefix: The prefix to use for the aliases to prevent conflicts with the + openage API namespace 'engine'. + :param import_tree: The import tree to set the aliases for. + """ + fqon = (modpack_name, "data", "tech", "generic") + current_node = import_tree.root + for part in fqon: + current_node = current_node.get_child(part) + + for child in current_node.children.values(): + current_node = child + + # These are folders and should have unique names + alias_name = "tech_" + current_node.name + + # One level deeper: This should be the nyan file + current_node = current_node.children[current_node.name] + + # Set the folder name as alias for the file + current_node.set_alias(alias_name) + + +def _add_modpack_civ_aliases(modpack_name: str, import_tree: ImportTree) -> None: + """ + Add aliases for civ objects from the modpack. + + :param modpack_name: The name of the modpack. + :param import_tree: The import tree to set the aliases for. + """ + fqon = (modpack_name, "data", "civ") + current_node = import_tree.root + for part in fqon: + current_node = current_node.get_child(part) + + for child in current_node.children.values(): + current_node = child + + # These are folders and should have unique names + alias_name = "civ_" + current_node.name + + # One level deeper: This should be the nyan file + current_node = current_node.children[current_node.name] + + # Set the folder name as alias for the file + current_node.set_alias(alias_name) diff --git a/openage/convert/processor/conversion/aoc/modpack_subprocessor.py b/openage/convert/processor/conversion/aoc/modpack_subprocessor.py index b842fc74c2..06505bc159 100644 --- a/openage/convert/processor/conversion/aoc/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/modpack_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-locals,too-many-branches,too-few-public-methods,too-many-statements @@ -15,6 +15,8 @@ from ....entity_object.export.formats.nyan_file import NyanFile from ....value_object.conversion.forward_ref import ForwardRef +from .modpack.import_tree import set_static_aliases + if typing.TYPE_CHECKING: from openage.convert.entity_object.conversion.aoc.genie_object_container\ import GenieObjectContainer @@ -124,7 +126,7 @@ def organize_nyan_objects(modpack: Modpack, full_data_set: GenieObjectContainer) for nyan_file in created_nyan_files.values(): nyan_file.set_import_tree(import_tree) - AoCModpackSubprocessor._set_static_aliases(modpack, import_tree) + set_static_aliases(modpack.name, import_tree) @staticmethod def organize_media_objects(modpack: Modpack, full_data_set: GenieObjectContainer) -> None: @@ -142,324 +144,3 @@ def organize_media_objects(modpack: Modpack, full_data_set: GenieObjectContainer for metadata_file in full_data_set.metadata_exports: modpack.add_metadata_export(metadata_file) - - @staticmethod - def _set_static_aliases(modpack: Modpack, import_tree: ImportTree) -> None: - """ - Set the aliases for the nyan import tree. The alias names - and affected nodes are hardcoded in this function. - """ - # Abilities from the openage API - import_tree.add_alias(("engine", "ability", "type"), "ability") - import_tree.add_alias(("engine", "ability", "property", "type"), "ability_prop") - - # Auxiliary objects - import_tree.add_alias( - ("engine", "util", "activity", "condition", "type"), "activity_condition" - ) - import_tree.add_alias(("engine", "util", "activity", "event", "type"), "activity_event") - import_tree.add_alias(("engine", "util", "activity", "node", "type"), "activity_node") - import_tree.add_alias(("engine", "util", "accuracy"), "accuracy") - import_tree.add_alias( - ("engine", "util", "animation_override"), "animation_override" - ) - import_tree.add_alias(("engine", "util", "attribute"), "attribute") - import_tree.add_alias(("engine", "util", "attribute_change_type", "type"), - "attribute_change_type") - import_tree.add_alias(("engine", "util", "calculation_type", "type"), "calculation_type") - import_tree.add_alias(("engine", "util", "setup"), "civ") - import_tree.add_alias(("engine", "util", "convert_type"), "convert_type") - import_tree.add_alias(("engine", "util", "cost", "type"), "cost_type") - import_tree.add_alias(("engine", "util", "create"), "create") - import_tree.add_alias(("engine", "util", "diplomatic_stance"), "diplo_stance") - import_tree.add_alias( - ("engine", "util", "diplomatic_stance", "type"), - "diplo_stance_type" - ) - import_tree.add_alias(("engine", "util", "distribution_type", "type"), "distribution_type") - import_tree.add_alias(("engine", "util", "dropoff_type", "type"), "dropoff_type") - import_tree.add_alias(("engine", "util", "exchange_mode", "type"), "exchange_mode") - import_tree.add_alias(("engine", "util", "exchange_rate"), "exchange_rate") - import_tree.add_alias(("engine", "util", "formation"), "formation") - import_tree.add_alias(("engine", "util", "game_entity"), "game_entity") - import_tree.add_alias( - ("engine", "util", "game_entity_formation"), "ge_formation" - ) - import_tree.add_alias(("engine", "util", "game_entity_stance", "type"), "ge_stance") - import_tree.add_alias(("engine", "util", "game_entity_type", "type"), "ge_type") - import_tree.add_alias( - ("engine", "util", "game_entity_type"), "game_entity_type" - ) - import_tree.add_alias(("engine", "util", "graphics"), "graphics") - import_tree.add_alias(("engine", "util", "herdable_mode", "type"), "herdable_mode") - import_tree.add_alias(("engine", "util", "hitbox"), "hitbox") - import_tree.add_alias(("engine", "util", "move_mode", "type"), "move_mode") - import_tree.add_alias(("engine", "util", "language"), "lang") - import_tree.add_alias(("engine", "util", "language", "translated", "type"), "translated") - import_tree.add_alias(("engine", "util", "logic", "gate", "type"), "logic_gate") - import_tree.add_alias(("engine", "util", "logic", "literal", "type"), "literal") - import_tree.add_alias(("engine", "util", "logic", "literal_scope", "type"), "literal_scope") - import_tree.add_alias(("engine", "util", "patch"), "patch") - import_tree.add_alias(("engine", "util", "patch", "property", "type"), "patch_prop") - import_tree.add_alias(("engine", "util", "path_type"), "path_type") - import_tree.add_alias(("engine", "util", "payment_mode", "type"), "payment_mode") - import_tree.add_alias(("engine", "util", "placement_mode", "type"), "placement_mode") - import_tree.add_alias(("engine", "util", "price_mode", "type"), "price_mode") - import_tree.add_alias(("engine", "util", "price_pool"), "price_pool") - import_tree.add_alias(("engine", "util", "production_mode", "type"), "production_mode") - import_tree.add_alias(("engine", "util", "progress"), "progress") - import_tree.add_alias(("engine", "util", "progress", "property", "type"), "progress_prop") - import_tree.add_alias(("engine", "util", "progress_status"), "progress_status") - import_tree.add_alias(("engine", "util", "progress_type", "type"), "progress_type") - import_tree.add_alias(("engine", "util", "research"), "research") - import_tree.add_alias(("engine", "util", "resource"), "resource") - import_tree.add_alias(("engine", "util", "resource_spot"), "resource_spot") - import_tree.add_alias(("engine", "util", "selection_box", "type"), "selection_box") - import_tree.add_alias(("engine", "util", "sound",), "sound") - import_tree.add_alias(("engine", "util", "state_machine"), "state_machine") - import_tree.add_alias(("engine", "util", "storage"), "storage") - import_tree.add_alias(("engine", "util", "target_mode", "type"), "target_mode") - import_tree.add_alias(("engine", "util", "tech"), "tech") - import_tree.add_alias(("engine", "util", "terrain"), "terrain") - import_tree.add_alias(("engine", "util", "terrain_type"), "terrain_type") - import_tree.add_alias(("engine", "util", "trade_route", "type"), "trade_route") - import_tree.add_alias(("engine", "util", "variant", "type"), "variant") - - # Effect objects - import_tree.add_alias(("engine", "effect", "property", "type"), "effect_prop") - import_tree.add_alias( - ("engine", "effect", "continuous", "flat_attribute_change", "type"), - "econt_flac" - ) - import_tree.add_alias( - ("engine", "effect", "continuous", "time_relative_progress", "type"), - "econt_trp" - ) - import_tree.add_alias( - ("engine", "effect", "continuous", "time_relative_attribute", "type"), - "econt_tra" - ) - import_tree.add_alias( - ("engine", "effect", "discrete", "convert", "type"), - "edisc_conv" - ) - import_tree.add_alias( - ("engine", "effect", "discrete", "flat_attribute_change", "type"), - "edisc_flac" - ) - import_tree.add_alias(("engine", "resistance", "property", "type"), "resist_prop") - import_tree.add_alias( - ("engine", "resistance", "continuous", "flat_attribute_change", "type"), - "rcont_flac" - ) - import_tree.add_alias( - ("engine", "resistance", "continuous", "time_relative_progress", "type"), - "rcont_trp" - ) - import_tree.add_alias( - ("engine", "resistance", "continuous", "time_relative_attribute", "type"), - "rcont_tra" - ) - import_tree.add_alias( - ("engine", "resistance", "discrete", "convert", "type"), - "rdisc_conv" - ) - import_tree.add_alias( - ("engine", "resistance", "discrete", "flat_attribute_change", "type"), - "rdisc_flac" - ) - - # Modifier objects - import_tree.add_alias( - ("engine", "modifier", "effect", "flat_attribute_change", "type"), - "me_flac" - ) - - # Aliases for objects from the modpack itself - - # Prefix aliases to prevent naming conflucts with 'engine' - prefix = modpack.name + "_" - - # Auxiliary objects - import_tree.add_alias( - (modpack.name, "data", "util", "attribute", "types"), - prefix + "attribute" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "attribute_change_type", "types"), - prefix + "attr_change_type" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "construct_type", "types"), - prefix + "construct_type" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "convert_type", "types"), - prefix + "convert_type" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "diplomatic_stance", "types"), - prefix + "diplo_stance" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "game_entity_type", "types"), - prefix + "ge_type" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "formation", "types"), - prefix + "formation" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "formation", "subformations"), - prefix + "subformations" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "language", "language"), - prefix + "lang" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "logic", "death", "death"), - "death_condition" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "logic", "garrison_empty", - "garrison_empty"), - "empty_garrison_condition" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "path_type", "types"), - prefix + "path_type" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "resource", "market_trading"), - prefix + "market_trading" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "resource", "types"), - prefix + "resource" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "terrain_type", "types"), - prefix + "terrain_type" - ) - - # Effect objects - import_tree.add_alias( - (modpack.name, "data", "effect", "discrete", "flat_attribute_change", - "fallback"), - "attack_fallback" - ) - import_tree.add_alias( - (modpack.name, "data", "effect", "discrete", "flat_attribute_change", - "min_damage"), - "min_damage" - ) - import_tree.add_alias( - (modpack.name, "data", "effect", "discrete", "flat_attribute_change", - "min_heal"), - "min_heal" - ) - - # Modifier objects - import_tree.add_alias( - (modpack.name, "data", "util", "modifier", "elevation_difference", - "elevation_difference"), - prefix + "mme_elev_high" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "modifier", "elevation_difference", - "elevation_difference"), - prefix + "mme_elev_low" - ) - import_tree.add_alias( - (modpack.name, "data", "util", "modifier", "flyover_cliff", - "flyover_cliff"), - prefix + "mme_cliff_attack" - ) - - # Terrain objects - import_tree.add_alias( - (modpack.name, "data", "terrain", "foundation", "foundation"), - prefix + "foundation" - ) - - # Generic aliases - # TODO: Make this less hacky - fqon = (modpack.name, "data", "game_entity", "generic") - current_node = import_tree.root - for part in fqon: - current_node = current_node.get_child(part) - - for child in current_node.children.values(): - current_node = child - - # These are folders and should have unique names - alias_name = f"ge_{current_node.name}" - - for subchild in current_node.children.values(): - if subchild.name in ("graphics", "sounds", "projectiles"): - continue - - if subchild.name.endswith("upgrade"): - alias = f"{alias_name}_{subchild.name}" - subchild.set_alias(alias) - continue - - # One level deeper: This should be the nyan file - current_node = subchild - - alias = f"ge_{current_node.name}" - - # Use the file name as alias for the file - current_node.set_alias(alias) - - fqon = (modpack.name, "data", "tech", "generic") - current_node = import_tree.root - for part in fqon: - current_node = current_node.get_child(part) - - for child in current_node.children.values(): - current_node = child - - # These are folders and should have unique names - alias_name = "tech_" + current_node.name - - # One level deeper: This should be the nyan file - current_node = current_node.children[current_node.name] - - # Set the folder name as alias for the file - current_node.set_alias(alias_name) - - fqon = (modpack.name, "data", "civ") - current_node = import_tree.root - for part in fqon: - current_node = current_node.get_child(part) - - for child in current_node.children.values(): - current_node = child - - # These are folders and should have unique names - alias_name = "civ_" + current_node.name - - # One level deeper: This should be the nyan file - current_node = current_node.children[current_node.name] - - # Set the folder name as alias for the file - current_node.set_alias(alias_name) - - fqon = (modpack.name, "data", "terrain") - current_node = import_tree.root - for part in fqon: - current_node = current_node.get_child(part) - - for child in current_node.children.values(): - current_node = child - - # These are folders and should have unique names - alias_name = "terrain_" + current_node.name - - # One level deeper: This should be the nyan file - current_node = current_node.children[current_node.name] - - # Set the folder name as alias for the file - current_node.set_alias(alias_name) From e3441231100b8be4a185ec16beb0cc63f97b3052 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 30 May 2025 15:07:55 +0200 Subject: [PATCH 117/163] convert: Refactor AoCNyanSubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../conversion/aoc/nyan/CMakeLists.txt | 11 + .../processor/conversion/aoc/nyan/__init__.py | 6 + .../processor/conversion/aoc/nyan/ambient.py | 106 ++ .../processor/conversion/aoc/nyan/building.py | 178 +++ .../processor/conversion/aoc/nyan/civ.py | 140 ++ .../conversion/aoc/nyan/projectile.py | 94 ++ .../processor/conversion/aoc/nyan/tech.py | 139 ++ .../processor/conversion/aoc/nyan/terrain.py | 211 +++ .../processor/conversion/aoc/nyan/unit.py | 231 ++++ .../processor/conversion/aoc/nyan/variant.py | 185 +++ .../conversion/aoc/nyan_subprocessor.py | 1177 +---------------- 12 files changed, 1320 insertions(+), 1159 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/nyan/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/nyan/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/nyan/ambient.py create mode 100644 openage/convert/processor/conversion/aoc/nyan/building.py create mode 100644 openage/convert/processor/conversion/aoc/nyan/civ.py create mode 100644 openage/convert/processor/conversion/aoc/nyan/projectile.py create mode 100644 openage/convert/processor/conversion/aoc/nyan/tech.py create mode 100644 openage/convert/processor/conversion/aoc/nyan/terrain.py create mode 100644 openage/convert/processor/conversion/aoc/nyan/unit.py create mode 100644 openage/convert/processor/conversion/aoc/nyan/variant.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index 23d68048cc..fe0a734ad2 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -24,4 +24,5 @@ add_subdirectory(effect) add_subdirectory(media) add_subdirectory(modifier) add_subdirectory(modpack) +add_subdirectory(nyan) add_subdirectory(resistance) diff --git a/openage/convert/processor/conversion/aoc/nyan/CMakeLists.txt b/openage/convert/processor/conversion/aoc/nyan/CMakeLists.txt new file mode 100644 index 0000000000..e04d250a37 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/CMakeLists.txt @@ -0,0 +1,11 @@ +add_py_modules( + __init__.py + ambient.py + building.py + civ.py + projectile.py + tech.py + terrain.py + unit.py + variant.py +) diff --git a/openage/convert/processor/conversion/aoc/nyan/__init__.py b/openage/convert/processor/conversion/aoc/nyan/__init__.py new file mode 100644 index 0000000000..37705b3e7b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert API-like objects to nyan objects. Subroutine of the +main AoC processor. +""" diff --git a/openage/convert/processor/conversion/aoc/nyan/ambient.py b/openage/convert/processor/conversion/aoc/nyan/ambient.py new file mode 100644 index 0000000000..844450e7f1 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/ambient.py @@ -0,0 +1,106 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert ambient groups to openage game entities. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ..ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieAmbientGroup + + +def ambient_group_to_game_entity(ambient_group: GenieAmbientGroup) -> None: + """ + Creates raw API objects for an ambient group. + + :param ambient_group: Unit line that gets converted to a game entity. + :type ambient_group: ..dataformat.converter_object.ConverterObjectGroup + """ + ambient_unit = ambient_group.get_head_unit() + ambient_id = ambient_group.get_head_unit_id() + + dataset = ambient_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[ambient_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[ambient_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[ambient_id][1]) + ambient_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give an ambient the types + # - util.game_entity_type.types.Ambient + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Ambient"].get_nyan_object( + ) + types_set.append(type_obj) + + unit_class = ambient_unit["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + interaction_mode = ambient_unit["interaction_mode"].value + + if interaction_mode >= 0: + abilities_set.append(AoCAbilitySubprocessor.death_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.resistance_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(ambient_group)) + + if interaction_mode >= 2: + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(ambient_group)) + + if not ambient_group.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(ambient_group)) + + if ambient_group.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(ambient_group)) + + # ======================================================================= + # Abilities + # ======================================================================= + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers_set = [] + + raw_api_object.add_raw_member("modifiers", modifiers_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") diff --git a/openage/convert/processor/conversion/aoc/nyan/building.py b/openage/convert/processor/conversion/aoc/nyan/building.py new file mode 100644 index 0000000000..8fb0c5e48d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/building.py @@ -0,0 +1,178 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert building lines to openage game entities. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ..ability_subprocessor import AoCAbilitySubprocessor +from ..auxiliary_subprocessor import AoCAuxiliarySubprocessor +from .projectile import projectiles_from_line + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup + + +def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: + """ + Creates raw API objects for a building line. + + :param building_line: Building line that gets converted to a game entity. + :type building_line: ..dataformat.converter_object.ConverterObjectGroup + """ + current_building = building_line.line[0] + current_building_id = building_line.get_head_unit_id() + dataset = building_line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[current_building_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[current_building_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[current_building_id][1]) + building_line.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give a building two types + # - util.game_entity_type.types.Building (if unit_type >= 80) + # - util.game_entity_type.types. (depending on the class) + # and additionally + # - util.game_entity_type.types.DropSite (only if this is used as a drop site) + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + unit_type = current_building["unit_type"].value + + if unit_type >= 80: + type_obj = dataset.pregen_nyan_objects[ + "util.game_entity_type.types.Building" + ].get_nyan_object() + types_set.append(type_obj) + + unit_class = current_building["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + if building_line.is_dropsite(): + type_obj = dataset.pregen_nyan_objects[ + "util.game_entity_type.types.DropSite" + ].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + abilities_set.append(AoCAbilitySubprocessor.attribute_change_tracker_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.death_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.resistance_ability(building_line)) + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.stop_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(building_line)) + + # Config abilities + if building_line.is_creatable(): + abilities_set.append(AoCAbilitySubprocessor.constructable_ability(building_line)) + + if not building_line.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(building_line)) + + if building_line.has_foundation(): + if building_line.get_class_id() == 49: + # Use OverlayTerrain for the farm terrain + abilities_set.append(AoCAbilitySubprocessor.overlay_terrain_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line, + terrain_id=27)) + + else: + abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line)) + + # Creation/Research abilities + if len(building_line.creates) > 0: + abilities_set.append(AoCAbilitySubprocessor.create_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.production_queue_ability(building_line)) + + if len(building_line.researches) > 0: + abilities_set.append(AoCAbilitySubprocessor.research_ability(building_line)) + + # Effect abilities + if building_line.is_projectile_shooter(): + abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(building_line, 7)) + abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(building_line)) + projectiles_from_line(building_line) + + # Storage abilities + if building_line.is_garrison(): + abilities_set.append(AoCAbilitySubprocessor.storage_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(building_line)) + + garrison_mode = building_line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.NATURAL: + abilities_set.append( + AoCAbilitySubprocessor.send_back_to_task_ability(building_line)) + + if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): + abilities_set.append(AoCAbilitySubprocessor.rally_point_ability(building_line)) + + # Resource abilities + if building_line.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(building_line)) + + if building_line.is_dropsite(): + abilities_set.append(AoCAbilitySubprocessor.drop_site_ability(building_line)) + + ability = AoCAbilitySubprocessor.provide_contingent_ability(building_line) + if ability: + abilities_set.append(ability) + + # Trade abilities + if building_line.is_trade_post(): + abilities_set.append(AoCAbilitySubprocessor.trade_post_ability(building_line)) + + if building_line.get_id() == 84: + # Market trading + abilities_set.extend(AoCAbilitySubprocessor.exchange_resources_ability(building_line)) + + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + raw_api_object.add_raw_member("modifiers", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Misc (Objects that are not used by the unit line itself, but use its values) + # ======================================================================= + if building_line.is_creatable(): + AoCAuxiliarySubprocessor.get_creatable_game_entity(building_line) diff --git a/openage/convert/processor/conversion/aoc/nyan/civ.py b/openage/convert/processor/conversion/aoc/nyan/civ.py new file mode 100644 index 0000000000..2ab30f98db --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/civ.py @@ -0,0 +1,140 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert civ groups to openage player setups. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..civ_subprocessor import AoCCivSubprocessor + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +@staticmethod +def civ_group_to_civ(civ_group: GenieCivilizationGroup) -> None: + """ + Creates raw API objects for a civ group. + + :param civ_group: Terrain group that gets converted to a tech. + :type civ_group: ..dataformat.converter_object.ConverterObjectGroup + """ + civ_id = civ_group.get_id() + + dataset = civ_group.data + + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + # Start with the Tech object + tech_name = civ_lookup_dict[civ_id][0] + raw_api_object = RawAPIObject(tech_name, tech_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.setup.PlayerSetup") + + obj_location = f"data/civ/{civ_lookup_dict[civ_id][1]}/" + + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(civ_lookup_dict[civ_id][1]) + civ_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{tech_name}.{tech_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{tech_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(civ_group, tech_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(civ_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Description + # ======================================================================= + description_ref = f"{tech_name}.{tech_name}Description" + description_raw_api_object = RawAPIObject(description_ref, + f"{tech_name}Description", + dataset.nyan_api_objects) + description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + description_location = ForwardRef(civ_group, tech_name) + description_raw_api_object.set_location(description_location) + + description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + description_forward_ref = ForwardRef(civ_group, description_ref) + raw_api_object.add_raw_member("description", + description_forward_ref, + "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(description_raw_api_object) + + # ======================================================================= + # Long description + # ======================================================================= + long_description_ref = f"{tech_name}.{tech_name}LongDescription" + long_description_raw_api_object = RawAPIObject(long_description_ref, + f"{tech_name}LongDescription", + dataset.nyan_api_objects) + long_description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + long_description_location = ForwardRef(civ_group, tech_name) + long_description_raw_api_object.set_location(long_description_location) + + long_description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + long_description_forward_ref = ForwardRef(civ_group, long_description_ref) + raw_api_object.add_raw_member("long_description", + long_description_forward_ref, + "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(long_description_raw_api_object) + + # ======================================================================= + # TODO: Leader names + # ======================================================================= + raw_api_object.add_raw_member("leader_names", + [], + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers = AoCCivSubprocessor.get_modifiers(civ_group) + raw_api_object.add_raw_member("modifiers", + modifiers, + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Starting resources + # ======================================================================= + resource_amounts = AoCCivSubprocessor.get_starting_resources(civ_group) + raw_api_object.add_raw_member("starting_resources", + resource_amounts, + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Game setup + # ======================================================================= + game_setup = AoCCivSubprocessor.get_civ_setup(civ_group) + raw_api_object.add_raw_member("game_setup", + game_setup, + "engine.util.setup.PlayerSetup") diff --git a/openage/convert/processor/conversion/aoc/nyan/projectile.py b/openage/convert/processor/conversion/aoc/nyan/projectile.py new file mode 100644 index 0000000000..a80591e378 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/projectile.py @@ -0,0 +1,94 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert projectiles to openage game entities. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ..ability_subprocessor import AoCAbilitySubprocessor +from ..modifier_subprocessor import AoCModifierSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def projectiles_from_line(line: GenieGameEntityGroup) -> None: + """ + Creates Projectile(GameEntity) raw API objects for a unit/building line. + + :param line: Line for which the projectiles are extracted. + :type line: ..dataformat.converter_object.ConverterObjectGroup + """ + current_unit = line.get_head_unit() + 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] + game_entity_filename = name_lookup_dict[current_unit_id][1] + + projectiles_location = f"data/game_entity/generic/{game_entity_filename}/projectiles/" + + projectile_indices = [] + projectile_primary = current_unit["projectile_id0"].value + if projectile_primary > -1: + projectile_indices.append(0) + + projectile_secondary = current_unit["projectile_id1"].value + if projectile_secondary > -1: + projectile_indices.append(1) + + for projectile_num in projectile_indices: + obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{projectile_num}" + obj_name = f"Projectile{projectile_num}" + proj_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) + proj_raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + proj_raw_api_object.set_location(projectiles_location) + proj_raw_api_object.set_filename(f"{game_entity_filename}_projectiles") + + # ======================================================================= + # Types + # ======================================================================= + types_set = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Projectile"].get_nyan_object() + ] + proj_raw_api_object.add_raw_member( + "types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + abilities_set.append(AoCAbilitySubprocessor.projectile_ability( + line, position=projectile_num)) + abilities_set.append(AoCAbilitySubprocessor.move_projectile_ability( + line, position=projectile_num)) + abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( + line, 7, False, projectile_num)) + # TODO: Death, Despawn + proj_raw_api_object.add_raw_member( + "abilities", abilities_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers_set = [] + + modifiers_set.append(AoCModifierSubprocessor.flyover_effect_modifier(line)) + modifiers_set.extend(AoCModifierSubprocessor.elevation_attack_modifiers(line)) + + proj_raw_api_object.add_raw_member( + "modifiers", modifiers_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Variants + # ======================================================================= + variants_set = [] + proj_raw_api_object.add_raw_member( + "variants", variants_set, "engine.util.game_entity.GameEntity") + + line.add_raw_api_object(proj_raw_api_object) diff --git a/openage/convert/processor/conversion/aoc/nyan/tech.py b/openage/convert/processor/conversion/aoc/nyan/tech.py new file mode 100644 index 0000000000..bc778d9024 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/tech.py @@ -0,0 +1,139 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert tech groups to openage techs. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import UnitLineUpgrade +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..auxiliary_subprocessor import AoCAuxiliarySubprocessor +from ..tech_subprocessor import AoCTechSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup + + +def tech_group_to_tech(tech_group: GenieTechEffectBundleGroup) -> None: + """ + Creates raw API objects for a tech group. + + :param tech_group: Tech group that gets converted to a tech. + :type tech_group: ..dataformat.converter_object.ConverterObjectGroup + """ + tech_id = tech_group.get_id() + + # Skip Dark Age tech + if tech_id == 104: + return + + dataset = tech_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + # Start with the Tech object + tech_name = tech_lookup_dict[tech_id][0] + raw_api_object = RawAPIObject(tech_name, tech_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.tech.Tech") + + if isinstance(tech_group, UnitLineUpgrade): + unit_line = dataset.unit_lines[tech_group.get_line_id()] + head_unit_id = unit_line.get_head_unit_id() + obj_location = f"data/game_entity/generic/{name_lookup_dict[head_unit_id][1]}/" + + else: + obj_location = f"data/tech/generic/{tech_lookup_dict[tech_id][1]}/" + + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(tech_lookup_dict[tech_id][1]) + tech_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Types + # ======================================================================= + raw_api_object.add_raw_member("types", [], "engine.util.tech.Tech") + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{tech_name}.{tech_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{tech_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(tech_group, tech_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(tech_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.tech.Tech") + tech_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Description + # ======================================================================= + description_ref = f"{tech_name}.{tech_name}Description" + description_raw_api_object = RawAPIObject(description_ref, + f"{tech_name}Description", + dataset.nyan_api_objects) + description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + description_location = ForwardRef(tech_group, tech_name) + description_raw_api_object.set_location(description_location) + + description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + description_forward_ref = ForwardRef(tech_group, description_ref) + raw_api_object.add_raw_member("description", + description_forward_ref, + "engine.util.tech.Tech") + tech_group.add_raw_api_object(description_raw_api_object) + + # ======================================================================= + # Long description + # ======================================================================= + long_description_ref = f"{tech_name}.{tech_name}LongDescription" + long_description_raw_api_object = RawAPIObject(long_description_ref, + f"{tech_name}LongDescription", + dataset.nyan_api_objects) + long_description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + long_description_location = ForwardRef(tech_group, tech_name) + long_description_raw_api_object.set_location(long_description_location) + + long_description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + long_description_forward_ref = ForwardRef(tech_group, long_description_ref) + raw_api_object.add_raw_member("long_description", + long_description_forward_ref, + "engine.util.tech.Tech") + tech_group.add_raw_api_object(long_description_raw_api_object) + + # ======================================================================= + # Updates + # ======================================================================= + patches = [] + patches.extend(AoCTechSubprocessor.get_patches(tech_group)) + raw_api_object.add_raw_member("updates", patches, "engine.util.tech.Tech") + + # ======================================================================= + # Misc (Objects that are not used by the tech group itself, but use its values) + # ======================================================================= + if tech_group.is_researchable(): + AoCAuxiliarySubprocessor.get_researchable_tech(tech_group) diff --git a/openage/convert/processor/conversion/aoc/nyan/terrain.py b/openage/convert/processor/conversion/aoc/nyan/terrain.py new file mode 100644 index 0000000000..84b868856a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/terrain.py @@ -0,0 +1,211 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert terrain groups to openage terrains. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.combined_terrain import CombinedTerrain +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.aoc.genie_terrain import GenieTerrainGroup + + +@staticmethod +def terrain_group_to_terrain(terrain_group: GenieTerrainGroup) -> None: + """ + Creates raw API objects for a terrain group. + + :param terrain_group: Terrain group that gets converted to a tech. + :type terrain_group: ..dataformat.converter_object.ConverterObjectGroup + """ + terrain_index = terrain_group.get_id() + + dataset = terrain_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) + terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( + dataset.game_version) + + # Start with the Terrain object + terrain_name = terrain_lookup_dict[terrain_index][1] + raw_api_object = RawAPIObject(terrain_name, terrain_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.terrain.Terrain") + obj_location = f"data/terrain/{terrain_lookup_dict[terrain_index][2]}/" + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(terrain_lookup_dict[terrain_index][2]) + terrain_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Types + # ======================================================================= + terrain_types = [] + + for terrain_type in terrain_type_lookup_dict.values(): + if terrain_index in terrain_type[0]: + type_name = f"util.terrain_type.types.{terrain_type[2]}" + type_obj = dataset.pregen_nyan_objects[type_name].get_nyan_object() + terrain_types.append(type_obj) + + raw_api_object.add_raw_member("types", terrain_types, "engine.util.terrain.Terrain") + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{terrain_name}.{terrain_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{terrain_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(terrain_group, terrain_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(terrain_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.terrain.Terrain") + terrain_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Sound + # ======================================================================= + sound_name = f"{terrain_name}.Sound" + sound_raw_api_object = RawAPIObject(sound_name, "Sound", + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(terrain_group, terrain_name) + sound_raw_api_object.set_location(sound_location) + + # Sounds for terrains don't exist in AoC + sounds = [] + + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + sounds, + "engine.util.sound.Sound") + + sound_forward_ref = ForwardRef(terrain_group, sound_name) + raw_api_object.add_raw_member("sound", + sound_forward_ref, + "engine.util.terrain.Terrain") + + terrain_group.add_raw_api_object(sound_raw_api_object) + + # ======================================================================= + # Ambience + # ======================================================================= + terrain = terrain_group.get_terrain() + ambients_count = terrain["terrain_units_used_count"].value + + ambience = [] + for ambient_index in range(ambients_count): + ambient_id = terrain["terrain_unit_id"][ambient_index].value + + if ambient_id == -1: + continue + + ambient_line = dataset.unit_ref[ambient_id] + ambient_name = name_lookup_dict[ambient_line.get_head_unit_id()][0] + + ambient_ref = f"{terrain_name}.Ambient{str(ambient_index)}" + ambient_raw_api_object = RawAPIObject(ambient_ref, + f"Ambient{str(ambient_index)}", + dataset.nyan_api_objects) + ambient_raw_api_object.add_raw_parent("engine.util.terrain.TerrainAmbient") + ambient_location = ForwardRef(terrain_group, terrain_name) + ambient_raw_api_object.set_location(ambient_location) + + # Game entity reference + ambient_line_forward_ref = ForwardRef(ambient_line, ambient_name) + ambient_raw_api_object.add_raw_member("object", + ambient_line_forward_ref, + "engine.util.terrain.TerrainAmbient") + + # Max density + max_density = terrain["terrain_unit_density"][ambient_index].value + ambient_raw_api_object.add_raw_member("max_density", + max_density, + "engine.util.terrain.TerrainAmbient") + + terrain_group.add_raw_api_object(ambient_raw_api_object) + terrain_ambient_forward_ref = ForwardRef(terrain_group, ambient_ref) + ambience.append(terrain_ambient_forward_ref) + + raw_api_object.add_raw_member("ambience", ambience, "engine.util.terrain.Terrain") + + # ======================================================================= + # Path Costs + # ======================================================================= + path_costs = {} + restrictions = dataset.genie_terrain_restrictions + + # Land grid + path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() + land_restrictions = restrictions[0x07] + if land_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Water grid + path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() + water_restrictions = restrictions[0x03] + if water_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Air grid (default accessible) + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + path_costs[path_type] = 1 + + raw_api_object.add_raw_member("path_costs", path_costs, "engine.util.terrain.Terrain") + + # ======================================================================= + # Graphic + # ======================================================================= + if terrain_group.has_subterrain(): + subterrain = terrain_group.get_subterrain() + terrain_id = subterrain.get_id() + + else: + terrain_id = terrain_group.get_id() + + # Create animation object + graphic_name = f"{terrain_name}.TerrainTexture" + graphic_raw_api_object = RawAPIObject(graphic_name, "TerrainTexture", + dataset.nyan_api_objects) + graphic_raw_api_object.add_raw_parent("engine.util.graphics.Terrain") + graphic_location = ForwardRef(terrain_group, terrain_name) + graphic_raw_api_object.set_location(graphic_location) + + if terrain_id in dataset.combined_terrains.keys(): + terrain_graphic = dataset.combined_terrains[terrain_id] + + else: + terrain_graphic = CombinedTerrain(terrain_id, + f"texture_{terrain_lookup_dict[terrain_index][2]}", + dataset) + dataset.combined_terrains.update({terrain_graphic.get_id(): terrain_graphic}) + + terrain_graphic.add_reference(graphic_raw_api_object) + + graphic_raw_api_object.add_raw_member("sprite", terrain_graphic, + "engine.util.graphics.Terrain") + + terrain_group.add_raw_api_object(graphic_raw_api_object) + graphic_forward_ref = ForwardRef(terrain_group, graphic_name) + raw_api_object.add_raw_member("terrain_graphic", graphic_forward_ref, + "engine.util.terrain.Terrain") diff --git a/openage/convert/processor/conversion/aoc/nyan/unit.py b/openage/convert/processor/conversion/aoc/nyan/unit.py new file mode 100644 index 0000000000..35a07ed21a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/unit.py @@ -0,0 +1,231 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert unit lines to openage game entities. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode, \ + GenieMonkGroup +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ..ability_subprocessor import AoCAbilitySubprocessor +from ..auxiliary_subprocessor import AoCAuxiliarySubprocessor +from ..modifier_subprocessor import AoCModifierSubprocessor +from .projectile import projectiles_from_line + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup + + +def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: + """ + Creates raw API objects for a unit line. + + :param unit_line: Unit line that gets converted to a game entity. + :type unit_line: ..dataformat.converter_object.ConverterObjectGroup + """ + current_unit = unit_line.get_head_unit() + current_unit_id = unit_line.get_head_unit_id() + + dataset = unit_line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[current_unit_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[current_unit_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[current_unit_id][1]) + unit_line.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give a unit two types + # - util.game_entity_type.types.Unit (if unit_type >= 70) + # - util.game_entity_type.types. (depending on the class) + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + unit_type = current_unit["unit_type"].value + + if unit_type >= 70: + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object( + ) + types_set.append(type_obj) + + unit_class = current_unit["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.los_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.move_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.resistance_ability(unit_line)) + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.stop_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.turn_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(unit_line)) + + # Creation + if len(unit_line.creates) > 0: + abilities_set.append(AoCAbilitySubprocessor.create_ability(unit_line)) + + # Config + ability = AoCAbilitySubprocessor.use_contingent_ability(unit_line) + if ability: + abilities_set.append(ability) + + if unit_line.get_head_unit_id() in (125, 692): + # Healing/Recharging attribute points (monks, berserks) + abilities_set.extend(AoCAbilitySubprocessor.regenerate_attribute_ability(unit_line)) + + # Applying effects and shooting projectiles + if unit_line.is_projectile_shooter(): + abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(unit_line, 7)) + projectiles_from_line(unit_line) + + elif unit_line.is_melee() or unit_line.is_ranged(): + if unit_line.has_command(7): + # Attack + abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( + unit_line, + 7, + unit_line.is_ranged())) + + if unit_line.has_command(101): + # Build + abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 101, + unit_line.is_ranged())) + + if unit_line.has_command(104): + # convert + abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( + unit_line, + 104, + unit_line.is_ranged())) + + if unit_line.has_command(105): + # Heal + abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 105, + unit_line.is_ranged())) + + if unit_line.has_command(106): + # Repair + abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 106, + unit_line.is_ranged())) + + # Formation/Stance + if not isinstance(unit_line, GenieVillagerGroup): + abilities_set.append(AoCAbilitySubprocessor.formation_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(unit_line)) + + # Storage abilities + if unit_line.is_garrison(): + abilities_set.append(AoCAbilitySubprocessor.storage_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(unit_line)) + + garrison_mode = unit_line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.MONK: + abilities_set.append(AoCAbilitySubprocessor.collect_storage_ability(unit_line)) + + if len(unit_line.garrison_locations) > 0: + ability = AoCAbilitySubprocessor.enter_container_ability(unit_line) + if ability: + abilities_set.append(ability) + + ability = AoCAbilitySubprocessor.exit_container_ability(unit_line) + if ability: + abilities_set.append(ability) + + if isinstance(unit_line, GenieMonkGroup): + abilities_set.append(AoCAbilitySubprocessor.transfer_storage_ability(unit_line)) + + # Resource abilities + if unit_line.is_gatherer(): + abilities_set.append(AoCAbilitySubprocessor.drop_resources_ability(unit_line)) + abilities_set.extend(AoCAbilitySubprocessor.gather_ability(unit_line)) + + # Resource storage + if unit_line.is_gatherer() or unit_line.has_command(111): + abilities_set.append(AoCAbilitySubprocessor.resource_storage_ability(unit_line)) + + if isinstance(unit_line, GenieVillagerGroup): + # Farm restocking + abilities_set.append(AoCAbilitySubprocessor.restock_ability(unit_line, 50)) + + if unit_line.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(unit_line)) + + if unit_type == 70 and unit_line.get_class_id() not in (9, 10, 58): + # Excludes trebuchets and animals + abilities_set.append(AoCAbilitySubprocessor.herd_ability(unit_line)) + + if unit_line.get_class_id() == 58: + abilities_set.append(AoCAbilitySubprocessor.herdable_ability(unit_line)) + + # Trade abilities + if unit_line.has_command(111): + abilities_set.append(AoCAbilitySubprocessor.trade_ability(unit_line)) + + # ======================================================================= + # TODO: Transform + # ======================================================================= + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers_set = [] + + if unit_line.has_command(7) and not unit_line.is_projectile_shooter(): + modifiers_set.extend(AoCModifierSubprocessor.elevation_attack_modifiers(unit_line)) + + if unit_line.is_gatherer(): + modifiers_set.extend(AoCModifierSubprocessor.gather_rate_modifier(unit_line)) + + raw_api_object.add_raw_member("modifiers", modifiers_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Misc (Objects that are not used by the unit line itself, but use its values) + # ======================================================================= + if unit_line.is_creatable(): + AoCAuxiliarySubprocessor.get_creatable_game_entity(unit_line) diff --git a/openage/convert/processor/conversion/aoc/nyan/variant.py b/openage/convert/processor/conversion/aoc/nyan/variant.py new file mode 100644 index 0000000000..3d683a48de --- /dev/null +++ b/openage/convert/processor/conversion/aoc/nyan/variant.py @@ -0,0 +1,185 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert variant groups to openage game entities. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..ability_subprocessor import AoCAbilitySubprocessor +from ..upgrade_ability_subprocessor import AoCUpgradeAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieVariantGroup + + +def variant_group_to_game_entity(variant_group: GenieVariantGroup) -> None: + """ + Creates raw API objects for a variant group. + + :param ambient_group: Unit line that gets converted to a game entity. + :type ambient_group: ..dataformat.converter_object.ConverterObjectGroup + """ + variant_main_unit = variant_group.get_head_unit() + variant_id = variant_group.get_head_unit_id() + + dataset = variant_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[variant_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[variant_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[variant_id][1]) + variant_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give variants the types + # - util.game_entity_type.types.Ambient + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Ambient"].get_nyan_object( + ) + types_set.append(type_obj) + + unit_class = variant_main_unit["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + abilities_set.append(AoCAbilitySubprocessor.death_ability(variant_group)) + abilities_set.append(AoCAbilitySubprocessor.despawn_ability(variant_group)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(variant_group)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(variant_group)) + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(variant_group)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(variant_group)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(variant_group)) + + if variant_main_unit.has_member("speed") and variant_main_unit["speed"].value > 0.0001\ + and variant_main_unit.has_member("command_sound_id"): + # TODO: Let variant groups be converted without having command_sound_id member + abilities_set.append(AoCAbilitySubprocessor.move_ability(variant_group)) + + if variant_group.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(variant_group)) + + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers_set = [] + + raw_api_object.add_raw_member("modifiers", modifiers_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Variants + # ======================================================================= + variants_set = [] + + variant_type = name_lookup_dict[variant_id][3] + + index = 0 + for variant in variant_group.line: + # Create a diff + diff_variant = variant_main_unit.diff(variant) + + if variant_type == "random": + variant_type_ref = "engine.util.variant.type.RandomVariant" + + elif variant_type == "angle": + variant_type_ref = "engine.util.variant.type.PerspectiveVariant" + + elif variant_type == "misc": + variant_type_ref = "engine.util.variant.type.MiscVariant" + + else: + raise ValueError(f"Unknown variant type: '{variant_type}' for '{game_entity_name}'.") + + variant_name = f"Variant{str(index)}" + variant_ref = f"{game_entity_name}.{variant_name}" + variant_raw_api_object = RawAPIObject(variant_ref, + variant_name, + dataset.nyan_api_objects) + variant_raw_api_object.add_raw_parent(variant_type_ref) + variant_location = ForwardRef(variant_group, game_entity_name) + variant_raw_api_object.set_location(variant_location) + + # Create patches for the diff + patches = [] + + patches.extend(AoCUpgradeAbilitySubprocessor.death_ability(variant_group, + variant_group, + variant_ref, + diff_variant)) + patches.extend(AoCUpgradeAbilitySubprocessor.despawn_ability(variant_group, + variant_group, + variant_ref, + diff_variant)) + patches.extend(AoCUpgradeAbilitySubprocessor.idle_ability(variant_group, + variant_group, + variant_ref, + diff_variant)) + patches.extend(AoCUpgradeAbilitySubprocessor.named_ability(variant_group, + variant_group, + variant_ref, + diff_variant)) + + if variant_main_unit.has_member("speed") and variant_main_unit["speed"].value > 0.0001\ + and variant_main_unit.has_member("command_sound_id"): + # TODO: Let variant groups be converted without having command_sound_id member: + patches.extend(AoCUpgradeAbilitySubprocessor.move_ability(variant_group, + variant_group, + variant_ref, + diff_variant)) + + # Changes + variant_raw_api_object.add_raw_member("changes", + patches, + "engine.util.variant.Variant") + + # Prority + variant_raw_api_object.add_raw_member("priority", + 1, + "engine.util.variant.Variant") + + if variant_type == "random": + variant_raw_api_object.add_raw_member("chance_share", + 1 / len(variant_group.line), + "engine.util.variant.type.RandomVariant") + + elif variant_type == "angle": + variant_raw_api_object.add_raw_member("angle", + index, + "engine.util.variant.type.PerspectiveVariant") + + variants_forward_ref = ForwardRef(variant_group, variant_ref) + variants_set.append(variants_forward_ref) + variant_group.add_raw_api_object(variant_raw_api_object) + + index += 1 + + raw_api_object.add_raw_member("variants", variants_set, + "engine.util.game_entity.GameEntity") diff --git a/openage/convert/processor/conversion/aoc/nyan_subprocessor.py b/openage/convert/processor/conversion/aoc/nyan_subprocessor.py index 7afbd38134..b23707a8a6 100644 --- a/openage/convert/processor/conversion/aoc/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/nyan_subprocessor.py @@ -1,9 +1,4 @@ -# Copyright 2019-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2019-2025 the openage authors. See copying.md for legal info. """ Convert API-like objects to nyan objects. Subroutine of the @@ -12,28 +7,17 @@ from __future__ import annotations import typing -from ....entity_object.conversion.aoc.genie_tech import UnitLineUpgrade -from ....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode, \ - GenieMonkGroup -from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup -from ....entity_object.conversion.combined_terrain import CombinedTerrain -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from .ability_subprocessor import AoCAbilitySubprocessor -from .auxiliary_subprocessor import AoCAuxiliarySubprocessor -from .civ_subprocessor import AoCCivSubprocessor -from .modifier_subprocessor import AoCModifierSubprocessor -from .tech_subprocessor import AoCTechSubprocessor -from .upgrade_ability_subprocessor import AoCUpgradeAbilitySubprocessor +from .nyan.ambient import ambient_group_to_game_entity +from .nyan.building import building_line_to_game_entity +from .nyan.civ import civ_group_to_civ +from .nyan.projectile import projectiles_from_line +from .nyan.tech import tech_group_to_tech +from .nyan.terrain import terrain_group_to_terrain +from .nyan.unit import unit_line_to_game_entity +from .nyan.variant import variant_group_to_game_entity if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup - from openage.convert.entity_object.conversion.aoc.genie_object_container import GenieObjectContainer - from openage.convert.entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup - from openage.convert.entity_object.conversion.aoc.genie_terrain import GenieTerrainGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup, \ - GenieUnitLineGroup, GenieBuildingLineGroup, GenieAmbientGroup, GenieVariantGroup + from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer class AoCNyanSubprocessor: @@ -164,1136 +148,11 @@ def _process_game_entities(cls, full_data_set: GenieObjectContainer) -> None: for civ_group in full_data_set.civ_groups.values(): cls.civ_group_to_civ(civ_group) - @staticmethod - def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: - """ - Creates raw API objects for a unit line. - - :param unit_line: Unit line that gets converted to a game entity. - :type unit_line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_unit = unit_line.get_head_unit() - current_unit_id = unit_line.get_head_unit_id() - - dataset = unit_line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[current_unit_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[current_unit_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[current_unit_id][1]) - unit_line.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give a unit two types - # - util.game_entity_type.types.Unit (if unit_type >= 70) - # - util.game_entity_type.types. (depending on the class) - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - unit_type = current_unit["unit_type"].value - - if unit_type >= 70: - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = current_unit["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.los_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.move_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.resistance_ability(unit_line)) - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.stop_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.turn_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(unit_line)) - - # Creation - if len(unit_line.creates) > 0: - abilities_set.append(AoCAbilitySubprocessor.create_ability(unit_line)) - - # Config - ability = AoCAbilitySubprocessor.use_contingent_ability(unit_line) - if ability: - abilities_set.append(ability) - - if unit_line.get_head_unit_id() in (125, 692): - # Healing/Recharging attribute points (monks, berserks) - abilities_set.extend(AoCAbilitySubprocessor.regenerate_attribute_ability(unit_line)) - - # Applying effects and shooting projectiles - if unit_line.is_projectile_shooter(): - abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(unit_line, 7)) - AoCNyanSubprocessor.projectiles_from_line(unit_line) - - elif unit_line.is_melee() or unit_line.is_ranged(): - if unit_line.has_command(7): - # Attack - abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability(unit_line, - 7, - unit_line.is_ranged())) - - if unit_line.has_command(101): - # Build - abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 101, - unit_line.is_ranged())) - - if unit_line.has_command(104): - # convert - abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability(unit_line, - 104, - unit_line.is_ranged())) - - if unit_line.has_command(105): - # Heal - abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 105, - unit_line.is_ranged())) - - if unit_line.has_command(106): - # Repair - abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 106, - unit_line.is_ranged())) - - # Formation/Stance - if not isinstance(unit_line, GenieVillagerGroup): - abilities_set.append(AoCAbilitySubprocessor.formation_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(unit_line)) - - # Storage abilities - if unit_line.is_garrison(): - abilities_set.append(AoCAbilitySubprocessor.storage_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(unit_line)) - - garrison_mode = unit_line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.MONK: - abilities_set.append(AoCAbilitySubprocessor.collect_storage_ability(unit_line)) - - if len(unit_line.garrison_locations) > 0: - ability = AoCAbilitySubprocessor.enter_container_ability(unit_line) - if ability: - abilities_set.append(ability) - - ability = AoCAbilitySubprocessor.exit_container_ability(unit_line) - if ability: - abilities_set.append(ability) - - if isinstance(unit_line, GenieMonkGroup): - abilities_set.append(AoCAbilitySubprocessor.transfer_storage_ability(unit_line)) - - # Resource abilities - if unit_line.is_gatherer(): - abilities_set.append(AoCAbilitySubprocessor.drop_resources_ability(unit_line)) - abilities_set.extend(AoCAbilitySubprocessor.gather_ability(unit_line)) - - # Resource storage - if unit_line.is_gatherer() or unit_line.has_command(111): - abilities_set.append(AoCAbilitySubprocessor.resource_storage_ability(unit_line)) - - if isinstance(unit_line, GenieVillagerGroup): - # Farm restocking - abilities_set.append(AoCAbilitySubprocessor.restock_ability(unit_line, 50)) - - if unit_line.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(unit_line)) - - if unit_type == 70 and unit_line.get_class_id() not in (9, 10, 58): - # Excludes trebuchets and animals - abilities_set.append(AoCAbilitySubprocessor.herd_ability(unit_line)) - - if unit_line.get_class_id() == 58: - abilities_set.append(AoCAbilitySubprocessor.herdable_ability(unit_line)) - - # Trade abilities - if unit_line.has_command(111): - abilities_set.append(AoCAbilitySubprocessor.trade_ability(unit_line)) - - # ======================================================================= - # TODO: Transform - # ======================================================================= - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers_set = [] - - if unit_line.has_command(7) and not unit_line.is_projectile_shooter(): - modifiers_set.extend(AoCModifierSubprocessor.elevation_attack_modifiers(unit_line)) - - if unit_line.is_gatherer(): - modifiers_set.extend(AoCModifierSubprocessor.gather_rate_modifier(unit_line)) - - raw_api_object.add_raw_member("modifiers", modifiers_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Misc (Objects that are not used by the unit line itself, but use its values) - # ======================================================================= - if unit_line.is_creatable(): - AoCAuxiliarySubprocessor.get_creatable_game_entity(unit_line) - - @staticmethod - def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: - """ - Creates raw API objects for a building line. - - :param building_line: Building line that gets converted to a game entity. - :type building_line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_building = building_line.line[0] - current_building_id = building_line.get_head_unit_id() - dataset = building_line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[current_building_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[current_building_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[current_building_id][1]) - building_line.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give a building two types - # - util.game_entity_type.types.Building (if unit_type >= 80) - # - util.game_entity_type.types. (depending on the class) - # and additionally - # - util.game_entity_type.types.DropSite (only if this is used as a drop site) - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - unit_type = current_building["unit_type"].value - - if unit_type >= 80: - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = current_building["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - if building_line.is_dropsite(): - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.DropSite"].get_nyan_object( - ) - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - abilities_set.append(AoCAbilitySubprocessor.attribute_change_tracker_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.death_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.resistance_ability(building_line)) - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.stop_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(building_line)) - - # Config abilities - if building_line.is_creatable(): - abilities_set.append(AoCAbilitySubprocessor.constructable_ability(building_line)) - - if not building_line.is_passable(): - abilities_set.append(AoCAbilitySubprocessor.pathable_ability(building_line)) - - if building_line.has_foundation(): - if building_line.get_class_id() == 49: - # Use OverlayTerrain for the farm terrain - abilities_set.append(AoCAbilitySubprocessor.overlay_terrain_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line, - terrain_id=27)) - - else: - abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line)) - - # Creation/Research abilities - if len(building_line.creates) > 0: - abilities_set.append(AoCAbilitySubprocessor.create_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.production_queue_ability(building_line)) - - if len(building_line.researches) > 0: - abilities_set.append(AoCAbilitySubprocessor.research_ability(building_line)) - - # Effect abilities - if building_line.is_projectile_shooter(): - abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(building_line, 7)) - abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(building_line)) - AoCNyanSubprocessor.projectiles_from_line(building_line) - - # Storage abilities - if building_line.is_garrison(): - abilities_set.append(AoCAbilitySubprocessor.storage_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(building_line)) - - garrison_mode = building_line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.NATURAL: - abilities_set.append( - AoCAbilitySubprocessor.send_back_to_task_ability(building_line)) - - if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): - abilities_set.append(AoCAbilitySubprocessor.rally_point_ability(building_line)) - - # Resource abilities - if building_line.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(building_line)) - - if building_line.is_dropsite(): - abilities_set.append(AoCAbilitySubprocessor.drop_site_ability(building_line)) - - ability = AoCAbilitySubprocessor.provide_contingent_ability(building_line) - if ability: - abilities_set.append(ability) - - # Trade abilities - if building_line.is_trade_post(): - abilities_set.append(AoCAbilitySubprocessor.trade_post_ability(building_line)) - - if building_line.get_id() == 84: - # Market trading - abilities_set.extend(AoCAbilitySubprocessor.exchange_resources_ability(building_line)) - - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - raw_api_object.add_raw_member("modifiers", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Misc (Objects that are not used by the unit line itself, but use its values) - # ======================================================================= - if building_line.is_creatable(): - AoCAuxiliarySubprocessor.get_creatable_game_entity(building_line) - - @staticmethod - def ambient_group_to_game_entity(ambient_group: GenieAmbientGroup) -> None: - """ - Creates raw API objects for an ambient group. - - :param ambient_group: Unit line that gets converted to a game entity. - :type ambient_group: ..dataformat.converter_object.ConverterObjectGroup - """ - ambient_unit = ambient_group.get_head_unit() - ambient_id = ambient_group.get_head_unit_id() - - dataset = ambient_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[ambient_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[ambient_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[ambient_id][1]) - ambient_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give an ambient the types - # - util.game_entity_type.types.Ambient - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Ambient"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = ambient_unit["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - interaction_mode = ambient_unit["interaction_mode"].value - - if interaction_mode >= 0: - abilities_set.append(AoCAbilitySubprocessor.death_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.resistance_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(ambient_group)) - - if interaction_mode >= 2: - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(ambient_group)) - - if not ambient_group.is_passable(): - abilities_set.append(AoCAbilitySubprocessor.pathable_ability(ambient_group)) - - if ambient_group.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(ambient_group)) - - # ======================================================================= - # Abilities - # ======================================================================= - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers_set = [] - - raw_api_object.add_raw_member("modifiers", modifiers_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") - - @staticmethod - def variant_group_to_game_entity(variant_group: GenieVariantGroup) -> None: - """ - Creates raw API objects for a variant group. - - :param ambient_group: Unit line that gets converted to a game entity. - :type ambient_group: ..dataformat.converter_object.ConverterObjectGroup - """ - variant_main_unit = variant_group.get_head_unit() - variant_id = variant_group.get_head_unit_id() - - dataset = variant_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[variant_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[variant_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[variant_id][1]) - variant_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give variants the types - # - util.game_entity_type.types.Ambient - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Ambient"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = variant_main_unit["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - abilities_set.append(AoCAbilitySubprocessor.death_ability(variant_group)) - abilities_set.append(AoCAbilitySubprocessor.despawn_ability(variant_group)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(variant_group)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(variant_group)) - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(variant_group)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(variant_group)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(variant_group)) - - if variant_main_unit.has_member("speed") and variant_main_unit["speed"].value > 0.0001\ - and variant_main_unit.has_member("command_sound_id"): - # TODO: Let variant groups be converted without having command_sound_id member - abilities_set.append(AoCAbilitySubprocessor.move_ability(variant_group)) - - if variant_group.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(variant_group)) - - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers_set = [] - - raw_api_object.add_raw_member("modifiers", modifiers_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Variants - # ======================================================================= - variants_set = [] - - variant_type = name_lookup_dict[variant_id][3] - - index = 0 - for variant in variant_group.line: - # Create a diff - diff_variant = variant_main_unit.diff(variant) - - if variant_type == "random": - variant_type_ref = "engine.util.variant.type.RandomVariant" - - elif variant_type == "angle": - variant_type_ref = "engine.util.variant.type.PerspectiveVariant" - - elif variant_type == "misc": - variant_type_ref = "engine.util.variant.type.MiscVariant" - - variant_name = f"Variant{str(index)}" - variant_ref = f"{game_entity_name}.{variant_name}" - variant_raw_api_object = RawAPIObject(variant_ref, - variant_name, - dataset.nyan_api_objects) - variant_raw_api_object.add_raw_parent(variant_type_ref) - variant_location = ForwardRef(variant_group, game_entity_name) - variant_raw_api_object.set_location(variant_location) - - # Create patches for the diff - patches = [] - - patches.extend(AoCUpgradeAbilitySubprocessor.death_ability(variant_group, - variant_group, - variant_ref, - diff_variant)) - patches.extend(AoCUpgradeAbilitySubprocessor.despawn_ability(variant_group, - variant_group, - variant_ref, - diff_variant)) - patches.extend(AoCUpgradeAbilitySubprocessor.idle_ability(variant_group, - variant_group, - variant_ref, - diff_variant)) - patches.extend(AoCUpgradeAbilitySubprocessor.named_ability(variant_group, - variant_group, - variant_ref, - diff_variant)) - - if variant_main_unit.has_member("speed") and variant_main_unit["speed"].value > 0.0001\ - and variant_main_unit.has_member("command_sound_id"): - # TODO: Let variant groups be converted without having command_sound_id member: - patches.extend(AoCUpgradeAbilitySubprocessor.move_ability(variant_group, - variant_group, - variant_ref, - diff_variant)) - - # Changes - variant_raw_api_object.add_raw_member("changes", - patches, - "engine.util.variant.Variant") - - # Prority - variant_raw_api_object.add_raw_member("priority", - 1, - "engine.util.variant.Variant") - - if variant_type == "random": - variant_raw_api_object.add_raw_member("chance_share", - 1 / len(variant_group.line), - "engine.util.variant.type.RandomVariant") - - elif variant_type == "angle": - variant_raw_api_object.add_raw_member("angle", - index, - "engine.util.variant.type.PerspectiveVariant") - - variants_forward_ref = ForwardRef(variant_group, variant_ref) - variants_set.append(variants_forward_ref) - variant_group.add_raw_api_object(variant_raw_api_object) - - index += 1 - - raw_api_object.add_raw_member("variants", variants_set, - "engine.util.game_entity.GameEntity") - - @staticmethod - def tech_group_to_tech(tech_group: GenieTechEffectBundleGroup) -> None: - """ - Creates raw API objects for a tech group. - - :param tech_group: Tech group that gets converted to a tech. - :type tech_group: ..dataformat.converter_object.ConverterObjectGroup - """ - tech_id = tech_group.get_id() - - # Skip Dark Age tech - if tech_id == 104: - return - - dataset = tech_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - # Start with the Tech object - tech_name = tech_lookup_dict[tech_id][0] - raw_api_object = RawAPIObject(tech_name, tech_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.tech.Tech") - - if isinstance(tech_group, UnitLineUpgrade): - unit_line = dataset.unit_lines[tech_group.get_line_id()] - head_unit_id = unit_line.get_head_unit_id() - obj_location = f"data/game_entity/generic/{name_lookup_dict[head_unit_id][1]}/" - - else: - obj_location = f"data/tech/generic/{tech_lookup_dict[tech_id][1]}/" - - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(tech_lookup_dict[tech_id][1]) - tech_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Types - # ======================================================================= - raw_api_object.add_raw_member("types", [], "engine.util.tech.Tech") - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{tech_name}.{tech_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{tech_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(tech_group, tech_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(tech_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.tech.Tech") - tech_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Description - # ======================================================================= - description_ref = f"{tech_name}.{tech_name}Description" - description_raw_api_object = RawAPIObject(description_ref, - f"{tech_name}Description", - dataset.nyan_api_objects) - description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - description_location = ForwardRef(tech_group, tech_name) - description_raw_api_object.set_location(description_location) - - description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - description_forward_ref = ForwardRef(tech_group, description_ref) - raw_api_object.add_raw_member("description", - description_forward_ref, - "engine.util.tech.Tech") - tech_group.add_raw_api_object(description_raw_api_object) - - # ======================================================================= - # Long description - # ======================================================================= - long_description_ref = f"{tech_name}.{tech_name}LongDescription" - long_description_raw_api_object = RawAPIObject(long_description_ref, - f"{tech_name}LongDescription", - dataset.nyan_api_objects) - long_description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - long_description_location = ForwardRef(tech_group, tech_name) - long_description_raw_api_object.set_location(long_description_location) - - long_description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - long_description_forward_ref = ForwardRef(tech_group, long_description_ref) - raw_api_object.add_raw_member("long_description", - long_description_forward_ref, - "engine.util.tech.Tech") - tech_group.add_raw_api_object(long_description_raw_api_object) - - # ======================================================================= - # Updates - # ======================================================================= - patches = [] - patches.extend(AoCTechSubprocessor.get_patches(tech_group)) - raw_api_object.add_raw_member("updates", patches, "engine.util.tech.Tech") - - # ======================================================================= - # Misc (Objects that are not used by the tech group itself, but use its values) - # ======================================================================= - if tech_group.is_researchable(): - AoCAuxiliarySubprocessor.get_researchable_tech(tech_group) - - @staticmethod - def terrain_group_to_terrain(terrain_group: GenieTerrainGroup) -> None: - """ - Creates raw API objects for a terrain group. - - :param terrain_group: Terrain group that gets converted to a tech. - :type terrain_group: ..dataformat.converter_object.ConverterObjectGroup - """ - terrain_index = terrain_group.get_id() - - dataset = terrain_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) - terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( - dataset.game_version) - - # Start with the Terrain object - terrain_name = terrain_lookup_dict[terrain_index][1] - raw_api_object = RawAPIObject(terrain_name, terrain_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.terrain.Terrain") - obj_location = f"data/terrain/{terrain_lookup_dict[terrain_index][2]}/" - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(terrain_lookup_dict[terrain_index][2]) - terrain_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Types - # ======================================================================= - terrain_types = [] - - for terrain_type in terrain_type_lookup_dict.values(): - if terrain_index in terrain_type[0]: - type_name = f"util.terrain_type.types.{terrain_type[2]}" - type_obj = dataset.pregen_nyan_objects[type_name].get_nyan_object() - terrain_types.append(type_obj) - - raw_api_object.add_raw_member("types", terrain_types, "engine.util.terrain.Terrain") - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{terrain_name}.{terrain_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{terrain_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(terrain_group, terrain_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(terrain_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.terrain.Terrain") - terrain_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Sound - # ======================================================================= - sound_name = f"{terrain_name}.Sound" - sound_raw_api_object = RawAPIObject(sound_name, "Sound", - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(terrain_group, terrain_name) - sound_raw_api_object.set_location(sound_location) - - # Sounds for terrains don't exist in AoC - sounds = [] - - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - sounds, - "engine.util.sound.Sound") - - sound_forward_ref = ForwardRef(terrain_group, sound_name) - raw_api_object.add_raw_member("sound", - sound_forward_ref, - "engine.util.terrain.Terrain") - - terrain_group.add_raw_api_object(sound_raw_api_object) - - # ======================================================================= - # Ambience - # ======================================================================= - terrain = terrain_group.get_terrain() - ambients_count = terrain["terrain_units_used_count"].value - - ambience = [] - for ambient_index in range(ambients_count): - ambient_id = terrain["terrain_unit_id"][ambient_index].value - - if ambient_id == -1: - continue - - ambient_line = dataset.unit_ref[ambient_id] - ambient_name = name_lookup_dict[ambient_line.get_head_unit_id()][0] - - ambient_ref = f"{terrain_name}.Ambient{str(ambient_index)}" - ambient_raw_api_object = RawAPIObject(ambient_ref, - f"Ambient{str(ambient_index)}", - dataset.nyan_api_objects) - ambient_raw_api_object.add_raw_parent("engine.util.terrain.TerrainAmbient") - ambient_location = ForwardRef(terrain_group, terrain_name) - ambient_raw_api_object.set_location(ambient_location) - - # Game entity reference - ambient_line_forward_ref = ForwardRef(ambient_line, ambient_name) - ambient_raw_api_object.add_raw_member("object", - ambient_line_forward_ref, - "engine.util.terrain.TerrainAmbient") - - # Max density - max_density = terrain["terrain_unit_density"][ambient_index].value - ambient_raw_api_object.add_raw_member("max_density", - max_density, - "engine.util.terrain.TerrainAmbient") - - terrain_group.add_raw_api_object(ambient_raw_api_object) - terrain_ambient_forward_ref = ForwardRef(terrain_group, ambient_ref) - ambience.append(terrain_ambient_forward_ref) - - raw_api_object.add_raw_member("ambience", ambience, "engine.util.terrain.Terrain") - - # ======================================================================= - # Path Costs - # ======================================================================= - path_costs = {} - restrictions = dataset.genie_terrain_restrictions - - # Land grid - path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() - land_restrictions = restrictions[0x07] - if land_restrictions.is_accessible(terrain_index): - path_costs[path_type] = 1 - - else: - path_costs[path_type] = 255 - - # Water grid - path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() - water_restrictions = restrictions[0x03] - if water_restrictions.is_accessible(terrain_index): - path_costs[path_type] = 1 - - else: - path_costs[path_type] = 255 - - # Air grid (default accessible) - path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() - path_costs[path_type] = 1 - - raw_api_object.add_raw_member("path_costs", path_costs, "engine.util.terrain.Terrain") - - # ======================================================================= - # Graphic - # ======================================================================= - if terrain_group.has_subterrain(): - subterrain = terrain_group.get_subterrain() - terrain_id = subterrain.get_id() - - else: - terrain_id = terrain_group.get_id() - - # Create animation object - graphic_name = f"{terrain_name}.TerrainTexture" - graphic_raw_api_object = RawAPIObject(graphic_name, "TerrainTexture", - dataset.nyan_api_objects) - graphic_raw_api_object.add_raw_parent("engine.util.graphics.Terrain") - graphic_location = ForwardRef(terrain_group, terrain_name) - graphic_raw_api_object.set_location(graphic_location) - - if terrain_id in dataset.combined_terrains.keys(): - terrain_graphic = dataset.combined_terrains[terrain_id] - - else: - terrain_graphic = CombinedTerrain(terrain_id, - f"texture_{terrain_lookup_dict[terrain_index][2]}", - dataset) - dataset.combined_terrains.update({terrain_graphic.get_id(): terrain_graphic}) - - terrain_graphic.add_reference(graphic_raw_api_object) - - graphic_raw_api_object.add_raw_member("sprite", terrain_graphic, - "engine.util.graphics.Terrain") - - terrain_group.add_raw_api_object(graphic_raw_api_object) - graphic_forward_ref = ForwardRef(terrain_group, graphic_name) - raw_api_object.add_raw_member("terrain_graphic", graphic_forward_ref, - "engine.util.terrain.Terrain") - - @staticmethod - def civ_group_to_civ(civ_group: GenieCivilizationGroup) -> None: - """ - Creates raw API objects for a civ group. - - :param civ_group: Terrain group that gets converted to a tech. - :type civ_group: ..dataformat.converter_object.ConverterObjectGroup - """ - civ_id = civ_group.get_id() - - dataset = civ_group.data - - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - # Start with the Tech object - tech_name = civ_lookup_dict[civ_id][0] - raw_api_object = RawAPIObject(tech_name, tech_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.setup.PlayerSetup") - - obj_location = f"data/civ/{civ_lookup_dict[civ_id][1]}/" - - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(civ_lookup_dict[civ_id][1]) - civ_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{tech_name}.{tech_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{tech_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(civ_group, tech_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(civ_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Description - # ======================================================================= - description_ref = f"{tech_name}.{tech_name}Description" - description_raw_api_object = RawAPIObject(description_ref, - f"{tech_name}Description", - dataset.nyan_api_objects) - description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - description_location = ForwardRef(civ_group, tech_name) - description_raw_api_object.set_location(description_location) - - description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - description_forward_ref = ForwardRef(civ_group, description_ref) - raw_api_object.add_raw_member("description", - description_forward_ref, - "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(description_raw_api_object) - - # ======================================================================= - # Long description - # ======================================================================= - long_description_ref = f"{tech_name}.{tech_name}LongDescription" - long_description_raw_api_object = RawAPIObject(long_description_ref, - f"{tech_name}LongDescription", - dataset.nyan_api_objects) - long_description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - long_description_location = ForwardRef(civ_group, tech_name) - long_description_raw_api_object.set_location(long_description_location) - - long_description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - long_description_forward_ref = ForwardRef(civ_group, long_description_ref) - raw_api_object.add_raw_member("long_description", - long_description_forward_ref, - "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(long_description_raw_api_object) - - # ======================================================================= - # TODO: Leader names - # ======================================================================= - raw_api_object.add_raw_member("leader_names", - [], - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers = AoCCivSubprocessor.get_modifiers(civ_group) - raw_api_object.add_raw_member("modifiers", - modifiers, - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Starting resources - # ======================================================================= - resource_amounts = AoCCivSubprocessor.get_starting_resources(civ_group) - raw_api_object.add_raw_member("starting_resources", - resource_amounts, - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Game setup - # ======================================================================= - game_setup = AoCCivSubprocessor.get_civ_setup(civ_group) - raw_api_object.add_raw_member("game_setup", - game_setup, - "engine.util.setup.PlayerSetup") - - @staticmethod - def projectiles_from_line(line: GenieGameEntityGroup) -> None: - """ - Creates Projectile(GameEntity) raw API objects for a unit/building line. - - :param line: Line for which the projectiles are extracted. - :type line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_unit = line.get_head_unit() - 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] - game_entity_filename = name_lookup_dict[current_unit_id][1] - - projectiles_location = f"data/game_entity/generic/{game_entity_filename}/projectiles/" - - projectile_indices = [] - projectile_primary = current_unit["projectile_id0"].value - if projectile_primary > -1: - projectile_indices.append(0) - - projectile_secondary = current_unit["projectile_id1"].value - if projectile_secondary > -1: - projectile_indices.append(1) - - for projectile_num in projectile_indices: - obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{projectile_num}" - obj_name = f"Projectile{projectile_num}" - proj_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) - proj_raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - proj_raw_api_object.set_location(projectiles_location) - proj_raw_api_object.set_filename(f"{game_entity_filename}_projectiles") - - # ======================================================================= - # Types - # ======================================================================= - types_set = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Projectile"].get_nyan_object()] - proj_raw_api_object.add_raw_member( - "types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - abilities_set.append(AoCAbilitySubprocessor.projectile_ability( - line, position=projectile_num)) - abilities_set.append(AoCAbilitySubprocessor.move_projectile_ability( - line, position=projectile_num)) - abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( - line, 7, False, projectile_num)) - # TODO: Death, Despawn - proj_raw_api_object.add_raw_member( - "abilities", abilities_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers_set = [] - - modifiers_set.append(AoCModifierSubprocessor.flyover_effect_modifier(line)) - modifiers_set.extend(AoCModifierSubprocessor.elevation_attack_modifiers(line)) - - proj_raw_api_object.add_raw_member( - "modifiers", modifiers_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Variants - # ======================================================================= - variants_set = [] - proj_raw_api_object.add_raw_member( - "variants", variants_set, "engine.util.game_entity.GameEntity") - - line.add_raw_api_object(proj_raw_api_object) + ambient_group_to_game_entity = staticmethod(ambient_group_to_game_entity) + building_line_to_game_entity = staticmethod(building_line_to_game_entity) + civ_group_to_civ = staticmethod(civ_group_to_civ) + projectiles_from_line = staticmethod(projectiles_from_line) + tech_group_to_tech = staticmethod(tech_group_to_tech) + terrain_group_to_terrain = staticmethod(terrain_group_to_terrain) + unit_line_to_game_entity = staticmethod(unit_line_to_game_entity) + variant_group_to_game_entity = staticmethod(variant_group_to_game_entity) From 6f54eecc483416993aa00652028c930ae1c92e45 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 31 May 2025 01:43:17 +0200 Subject: [PATCH 118/163] convert: Refactor AoCPregenSubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../conversion/aoc/pregen/CMakeLists.txt | 18 + .../conversion/aoc/pregen/__init__.py | 7 + .../conversion/aoc/pregen/activity.py | 505 ++++ .../conversion/aoc/pregen/attribute.py | 142 + .../conversion/aoc/pregen/condition.py | 154 + .../conversion/aoc/pregen/diplomatic.py | 127 + .../processor/conversion/aoc/pregen/effect.py | 579 ++++ .../processor/conversion/aoc/pregen/entity.py | 216 ++ .../conversion/aoc/pregen/exchange.py | 347 +++ .../conversion/aoc/pregen/formation.py | 250 ++ .../conversion/aoc/pregen/language.py | 47 + .../processor/conversion/aoc/pregen/misc.py | 1 + .../conversion/aoc/pregen/modifier.py | 189 ++ .../processor/conversion/aoc/pregen/path.py | 71 + .../conversion/aoc/pregen/resource.py | 265 ++ .../conversion/aoc/pregen/team_property.py | 52 + .../conversion/aoc/pregen/terrain.py | 47 + .../conversion/aoc/pregen_processor.py | 2520 +---------------- 19 files changed, 3066 insertions(+), 2472 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/pregen/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/pregen/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/activity.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/attribute.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/condition.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/diplomatic.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/effect.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/entity.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/exchange.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/formation.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/language.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/misc.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/modifier.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/path.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/resource.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/team_property.py create mode 100644 openage/convert/processor/conversion/aoc/pregen/terrain.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index fe0a734ad2..a4362f49e6 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -25,4 +25,5 @@ add_subdirectory(media) add_subdirectory(modifier) add_subdirectory(modpack) add_subdirectory(nyan) +add_subdirectory(pregen) add_subdirectory(resistance) diff --git a/openage/convert/processor/conversion/aoc/pregen/CMakeLists.txt b/openage/convert/processor/conversion/aoc/pregen/CMakeLists.txt new file mode 100644 index 0000000000..9c10033478 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/CMakeLists.txt @@ -0,0 +1,18 @@ +add_py_modules( + __init__.py + activity.py + attribute.py + condition.py + diplomatic.py + effect.py + entity.py + exchange.py + formation.py + language.py + misc.py + modifier.py + path.py + resource.py + team_property.py + terrain.py +) diff --git a/openage/convert/processor/conversion/aoc/pregen/__init__.py b/openage/convert/processor/conversion/aoc/pregen/__init__.py new file mode 100644 index 0000000000..c9eef96971 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/__init__.py @@ -0,0 +1,7 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates nyan objects for things that are hardcoded into the Genie Engine, +but configurable in openage, e.g. HP. +""" +# Copyright 2025-2025 the openage authors. See copying.md for legal info. diff --git a/openage/convert/processor/conversion/aoc/pregen/activity.py b/openage/convert/processor/conversion/aoc/pregen/activity.py new file mode 100644 index 0000000000..f308cfb406 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/activity.py @@ -0,0 +1,505 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create the activities for unit behaviour in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +# activity parent and location +ACTIVITY_PARENT = "engine.util.activity.Activity" +ACTIVITY_LOCATION = "data/util/activity/" + +# Node types +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" + +# 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 = ( + "engine.util.activity.switch_condition.type.NextCommand" +) + + +def generate_activities( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the activities for game entity behaviour. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_default_activity(full_data_set, pregen_converter_group) + _generate_unit_activity(full_data_set, pregen_converter_group) + + +def _generate_default_activity( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate a default activity with a start, idle and end node. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # Activity + default_ref_in_modpack = "util.activity.types.Default" + default_raw_api_object = RawAPIObject(default_ref_in_modpack, + "Default", api_objects, + ACTIVITY_LOCATION) + default_raw_api_object.set_filename("types") + default_raw_api_object.add_raw_parent(ACTIVITY_PARENT) + + start_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Default.Start") + default_raw_api_object.add_raw_member("start", start_forward_ref, + ACTIVITY_PARENT) + + pregen_converter_group.add_raw_api_object(default_raw_api_object) + pregen_nyan_objects.update({default_ref_in_modpack: default_raw_api_object}) + + unit_forward_ref = ForwardRef(pregen_converter_group, default_ref_in_modpack) + + # Start + start_ref_in_modpack = "util.activity.types.Default.Start" + start_raw_api_object = RawAPIObject(start_ref_in_modpack, + "Start", api_objects) + start_raw_api_object.set_location(unit_forward_ref) + start_raw_api_object.add_raw_parent(START_PARENT) + + idle_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Default.Idle") + start_raw_api_object.add_raw_member("next", idle_forward_ref, + START_PARENT) + + pregen_converter_group.add_raw_api_object(start_raw_api_object) + pregen_nyan_objects.update({start_ref_in_modpack: start_raw_api_object}) + + # Idle + idle_ref_in_modpack = "util.activity.types.Default.Idle" + idle_raw_api_object = RawAPIObject(idle_ref_in_modpack, + "Idle", api_objects) + idle_raw_api_object.set_location(unit_forward_ref) + idle_raw_api_object.add_raw_parent(ABILITY_PARENT) + + end_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Default.End") + idle_raw_api_object.add_raw_member("next", end_forward_ref, + ABILITY_PARENT) + idle_raw_api_object.add_raw_member("ability", + api_objects["engine.ability.type.Idle"], + ABILITY_PARENT) + + pregen_converter_group.add_raw_api_object(idle_raw_api_object) + pregen_nyan_objects.update({idle_ref_in_modpack: idle_raw_api_object}) + + # End + end_ref_in_modpack = "util.activity.types.Default.End" + end_raw_api_object = RawAPIObject(end_ref_in_modpack, + "End", api_objects) + end_raw_api_object.set_location(unit_forward_ref) + end_raw_api_object.add_raw_parent(END_PARENT) + + pregen_converter_group.add_raw_api_object(end_raw_api_object) + pregen_nyan_objects.update({end_ref_in_modpack: end_raw_api_object}) + + +def _generate_unit_activity( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup, +) -> None: + """ + Generate a unit activity with various nodes for unit commands. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + unit_ref_in_modpack = "util.activity.types.Unit" + unit_raw_api_object = RawAPIObject(unit_ref_in_modpack, + "Unit", api_objects, + ACTIVITY_LOCATION) + unit_raw_api_object.set_filename("types") + unit_raw_api_object.add_raw_parent(ACTIVITY_PARENT) + + start_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Start") + unit_raw_api_object.add_raw_member("start", start_forward_ref, + ACTIVITY_PARENT) + + pregen_converter_group.add_raw_api_object(unit_raw_api_object) + pregen_nyan_objects.update({unit_ref_in_modpack: unit_raw_api_object}) + + unit_forward_ref = ForwardRef(pregen_converter_group, unit_ref_in_modpack) + + # Start + start_ref_in_modpack = "util.activity.types.Unit.Start" + start_raw_api_object = RawAPIObject(start_ref_in_modpack, + "Start", api_objects) + start_raw_api_object.set_location(unit_forward_ref) + start_raw_api_object.add_raw_parent(START_PARENT) + + idle_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Idle") + start_raw_api_object.add_raw_member("next", idle_forward_ref, + START_PARENT) + + pregen_converter_group.add_raw_api_object(start_raw_api_object) + pregen_nyan_objects.update({start_ref_in_modpack: start_raw_api_object}) + + # Idle + idle_ref_in_modpack = "util.activity.types.Unit.Idle" + idle_raw_api_object = RawAPIObject(idle_ref_in_modpack, + "Idle", api_objects) + idle_raw_api_object.set_location(unit_forward_ref) + idle_raw_api_object.add_raw_parent(ABILITY_PARENT) + + queue_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.CheckQueue") + idle_raw_api_object.add_raw_member("next", queue_forward_ref, + ABILITY_PARENT) + idle_raw_api_object.add_raw_member("ability", + api_objects["engine.ability.type.Idle"], + ABILITY_PARENT) + + pregen_converter_group.add_raw_api_object(idle_raw_api_object) + pregen_nyan_objects.update({idle_ref_in_modpack: idle_raw_api_object}) + + # Check if command is in queue + queue_ref_in_modpack = "util.activity.types.Unit.CheckQueue" + queue_raw_api_object = RawAPIObject(queue_ref_in_modpack, + "CheckQueue", api_objects) + queue_raw_api_object.set_location(unit_forward_ref) + queue_raw_api_object.add_raw_parent(XOR_PARENT) + + condition_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.CommandInQueue") + queue_raw_api_object.add_raw_member("next", + [condition_forward_ref], + XOR_PARENT) + command_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.WaitForCommand") + queue_raw_api_object.add_raw_member("default", + command_forward_ref, + XOR_PARENT) + + pregen_converter_group.add_raw_api_object(queue_raw_api_object) + pregen_nyan_objects.update({queue_ref_in_modpack: queue_raw_api_object}) + + # condition for command in queue + condition_ref_in_modpack = "util.activity.types.Unit.CommandInQueue" + 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(COND_QUEUE_PARENT) + + branch_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.BranchCommand") + condition_raw_api_object.add_raw_member("next", + branch_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}) + + # Wait for Command + command_ref_in_modpack = "util.activity.types.Unit.WaitForCommand" + command_raw_api_object = RawAPIObject(command_ref_in_modpack, + "WaitForCommand", api_objects) + command_raw_api_object.set_location(unit_forward_ref) + command_raw_api_object.add_raw_parent(XOR_EVENT_PARENT) + + event_api_object = api_objects["engine.util.activity.event.type.CommandInQueue"] + branch_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.BranchCommand") + command_raw_api_object.add_raw_member("next", + {event_api_object: branch_forward_ref}, + XOR_EVENT_PARENT) + + pregen_converter_group.add_raw_api_object(command_raw_api_object) + pregen_nyan_objects.update({command_ref_in_modpack: command_raw_api_object}) + + # Branch on command type + branch_ref_in_modpack = "util.activity.types.Unit.BranchCommand" + 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_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_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 based on command + condition_ref_in_modpack = "util.activity.types.Unit.NextCommandSwitch" + 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(COND_COMMAND_SWITCH_PARENT) + + 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"]: ability_check_forward_ref, + api_objects["engine.util.command.type.Move"]: move_forward_ref, + } + condition_raw_api_object.add_raw_member("next", + next_nodes_lookup, + 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}) + + # 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, + "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) + move_to_target_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.MoveToTarget") + range_check_raw_api_object.add_raw_member("default", + move_to_target_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} + ) + + # 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.MoveToTarget"], + TASK_PARENT + ) + + pregen_converter_group.add_raw_api_object(move_to_target_raw_api_object) + pregen_nyan_objects.update( + {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, + "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.WaitAbility") + 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}) + + # Move + move_ref_in_modpack = "util.activity.types.Unit.Move" + move_raw_api_object = RawAPIObject(move_ref_in_modpack, + "Move", api_objects) + move_raw_api_object.set_location(unit_forward_ref) + move_raw_api_object.add_raw_parent(ABILITY_PARENT) + + wait_forward_ref = ForwardRef(pregen_converter_group, + "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", + api_objects["engine.ability.type.Move"], + ABILITY_PARENT) + + pregen_converter_group.add_raw_api_object(move_raw_api_object) + pregen_nyan_objects.update({move_ref_in_modpack: move_raw_api_object}) + + # 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) + 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"] + wait_raw_api_object.add_raw_member("next", + { + wait_finish: idle_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}) + + # End + end_ref_in_modpack = "util.activity.types.Unit.End" + end_raw_api_object = RawAPIObject(end_ref_in_modpack, + "End", api_objects) + end_raw_api_object.set_location(unit_forward_ref) + end_raw_api_object.add_raw_parent(END_PARENT) + + pregen_converter_group.add_raw_api_object(end_raw_api_object) + pregen_nyan_objects.update({end_ref_in_modpack: end_raw_api_object}) diff --git a/openage/convert/processor/conversion/aoc/pregen/attribute.py b/openage/convert/processor/conversion/aoc/pregen/attribute.py new file mode 100644 index 0000000000..bd3c7323e9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/attribute.py @@ -0,0 +1,142 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create the attributes used in AoC. + +TODO: Fill translations +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +ATTRIBUTE_PARENT = "engine.util.attribute.Attribute" +ATTRIBUTES_LOCATION = "data/util/attribute/" + + +def generate_attributes( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate Attribute objects. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_hp_attribute(full_data_set, pregen_converter_group) + _generate_faith_attribute(full_data_set, pregen_converter_group) + + +def _generate_hp_attribute( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the HP attribute. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + health_ref_in_modpack = "util.attribute.types.Health" + health_raw_api_object = RawAPIObject(health_ref_in_modpack, + "Health", api_objects, + ATTRIBUTES_LOCATION) + health_raw_api_object.set_filename("types") + health_raw_api_object.add_raw_parent(ATTRIBUTE_PARENT) + + name_forward_ref = ForwardRef(pregen_converter_group, + "util.attribute.types.Health.HealthName") + health_raw_api_object.add_raw_member("name", name_forward_ref, + ATTRIBUTE_PARENT) + abbrv_forward_ref = ForwardRef(pregen_converter_group, + "util.attribute.types.Health.HealthAbbreviation") + health_raw_api_object.add_raw_member("abbreviation", abbrv_forward_ref, + ATTRIBUTE_PARENT) + + pregen_converter_group.add_raw_api_object(health_raw_api_object) + pregen_nyan_objects.update({health_ref_in_modpack: health_raw_api_object}) + + name_value_parent = "engine.util.language.translated.type.TranslatedString" + health_name_ref_in_modpack = "util.attribute.types.Health.HealthName" + health_name_value = RawAPIObject(health_name_ref_in_modpack, "HealthName", + api_objects, ATTRIBUTES_LOCATION) + health_name_value.set_filename("types") + health_name_value.add_raw_parent(name_value_parent) + health_name_value.add_raw_member("translations", [], name_value_parent) + + pregen_converter_group.add_raw_api_object(health_name_value) + pregen_nyan_objects.update({health_name_ref_in_modpack: health_name_value}) + + abbrv_value_parent = "engine.util.language.translated.type.TranslatedString" + health_abbrv_ref_in_modpack = "util.attribute.types.Health.HealthAbbreviation" + health_abbrv_value = RawAPIObject(health_abbrv_ref_in_modpack, "HealthAbbreviation", + api_objects, ATTRIBUTES_LOCATION) + health_abbrv_value.set_filename("types") + health_abbrv_value.add_raw_parent(abbrv_value_parent) + health_abbrv_value.add_raw_member("translations", [], abbrv_value_parent) + + pregen_converter_group.add_raw_api_object(health_abbrv_value) + pregen_nyan_objects.update({health_abbrv_ref_in_modpack: health_abbrv_value}) + + +def _generate_faith_attribute( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Faith attribute. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + faith_ref_in_modpack = "util.attribute.types.Faith" + faith_raw_api_object = RawAPIObject(faith_ref_in_modpack, + "Faith", api_objects, + ATTRIBUTES_LOCATION) + faith_raw_api_object.set_filename("types") + faith_raw_api_object.add_raw_parent(ATTRIBUTE_PARENT) + + name_forward_ref = ForwardRef(pregen_converter_group, + "util.attribute.types.Faith.FaithName") + faith_raw_api_object.add_raw_member("name", name_forward_ref, + ATTRIBUTE_PARENT) + abbrv_forward_ref = ForwardRef(pregen_converter_group, + "util.attribute.types.Faith.FaithAbbreviation") + faith_raw_api_object.add_raw_member("abbreviation", abbrv_forward_ref, + ATTRIBUTE_PARENT) + + pregen_converter_group.add_raw_api_object(faith_raw_api_object) + pregen_nyan_objects.update({faith_ref_in_modpack: faith_raw_api_object}) + + name_value_parent = "engine.util.language.translated.type.TranslatedString" + faith_name_ref_in_modpack = "util.attribute.types.Faith.FaithName" + faith_name_value = RawAPIObject(faith_name_ref_in_modpack, "FaithName", + api_objects, ATTRIBUTES_LOCATION) + faith_name_value.set_filename("types") + faith_name_value.add_raw_parent(name_value_parent) + faith_name_value.add_raw_member("translations", [], name_value_parent) + + pregen_converter_group.add_raw_api_object(faith_name_value) + pregen_nyan_objects.update({faith_name_ref_in_modpack: faith_name_value}) + + abbrv_value_parent = "engine.util.language.translated.type.TranslatedString" + faith_abbrv_ref_in_modpack = "util.attribute.types.Faith.FaithAbbreviation" + faith_abbrv_value = RawAPIObject(faith_abbrv_ref_in_modpack, "FaithAbbreviation", + api_objects, ATTRIBUTES_LOCATION) + faith_abbrv_value.set_filename("types") + faith_abbrv_value.add_raw_parent(abbrv_value_parent) + faith_abbrv_value.add_raw_member("translations", [], abbrv_value_parent) + + pregen_converter_group.add_raw_api_object(faith_abbrv_value) + pregen_nyan_objects.update({faith_abbrv_ref_in_modpack: faith_abbrv_value}) diff --git a/openage/convert/processor/conversion/aoc/pregen/condition.py b/openage/convert/processor/conversion/aoc/pregen/condition.py new file mode 100644 index 0000000000..fb32c1cade --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/condition.py @@ -0,0 +1,154 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create conditions for AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +LOGIC_PARENT = "engine.util.logic.LogicElement" +LITERAL_PARENT = "engine.util.logic.literal.Literal" +INTERVAL_PARENT = "engine.util.logic.literal.type.AttributeBelowValue" + +SCOPE_PARENT = "engine.util.logic.literal_scope.LiteralScope" +SELF_SCOPE_PARENT = "engine.util.logic.literal_scope.type.Self" + + +def generate_death_condition( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate DeathCondition objects for unit and building deaths in AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + literal_location = "data/util/logic/death/" + + death_ref_in_modpack = "util.logic.literal.death.StandardHealthDeathLiteral" + literal_raw_api_object = RawAPIObject(death_ref_in_modpack, + "StandardHealthDeathLiteral", + api_objects, + literal_location) + literal_raw_api_object.set_filename("death") + literal_raw_api_object.add_raw_parent(INTERVAL_PARENT) + + # Literal will not default to 'True' when it was fulfilled once + literal_raw_api_object.add_raw_member("only_once", False, LOGIC_PARENT) + + # Scope + scope_forward_ref = ForwardRef(pregen_converter_group, + "util.logic.literal_scope.death.StandardHealthDeathScope") + literal_raw_api_object.add_raw_member("scope", + scope_forward_ref, + LITERAL_PARENT) + + # Attribute + health_forward_ref = ForwardRef(pregen_converter_group, + "util.attribute.types.Health") + literal_raw_api_object.add_raw_member("attribute", + health_forward_ref, + INTERVAL_PARENT) + + # sidenote: Apparently this is actually HP<1 in Genie + # (https://youtu.be/FdBk8zGbE7U?t=7m16s) + literal_raw_api_object.add_raw_member("threshold", + 1, + INTERVAL_PARENT) + + pregen_converter_group.add_raw_api_object(literal_raw_api_object) + pregen_nyan_objects.update({death_ref_in_modpack: literal_raw_api_object}) + + # LiteralScope + death_scope_ref_in_modpack = "util.logic.literal_scope.death.StandardHealthDeathScope" + scope_raw_api_object = RawAPIObject(death_scope_ref_in_modpack, + "StandardHealthDeathScope", + api_objects) + scope_location = ForwardRef(pregen_converter_group, death_ref_in_modpack) + scope_raw_api_object.set_location(scope_location) + scope_raw_api_object.add_raw_parent(SELF_SCOPE_PARENT) + + scope_diplomatic_stances = [api_objects["engine.util.diplomatic_stance.type.Self"]] + scope_raw_api_object.add_raw_member("stances", + scope_diplomatic_stances, + SCOPE_PARENT) + + pregen_converter_group.add_raw_api_object(scope_raw_api_object) + pregen_nyan_objects.update({death_scope_ref_in_modpack: scope_raw_api_object}) + + +def generate_garrison_empty_condition( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate condition objects for emptying garrisoned buildings when they have + low HP in AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + literal_location = "data/util/logic/garrison_empty/" + + garrison_literal_ref_in_modpack = "util.logic.literal.garrison.BuildingDamageEmpty" + literal_raw_api_object = RawAPIObject(garrison_literal_ref_in_modpack, + "BuildingDamageEmptyLiteral", + api_objects, + literal_location) + literal_raw_api_object.set_filename("garrison_empty") + literal_raw_api_object.add_raw_parent(INTERVAL_PARENT) + + # Literal will not default to 'True' when it was fulfilled once + literal_raw_api_object.add_raw_member("only_once", False, LOGIC_PARENT) + + # Scope + scope_forward_ref = ForwardRef(pregen_converter_group, + "util.logic.literal_scope.garrison.BuildingDamageEmptyScope") + literal_raw_api_object.add_raw_member("scope", + scope_forward_ref, + LITERAL_PARENT) + + # Attribute + health_forward_ref = ForwardRef(pregen_converter_group, + "util.attribute.types.Health") + literal_raw_api_object.add_raw_member("attribute", + health_forward_ref, + INTERVAL_PARENT) + + # Threshhold + literal_raw_api_object.add_raw_member("threshold", + 0.2, + INTERVAL_PARENT) + + pregen_converter_group.add_raw_api_object(literal_raw_api_object) + pregen_nyan_objects.update({garrison_literal_ref_in_modpack: literal_raw_api_object}) + + # LiteralScope + garrison_scope_ref_in_modpack = "util.logic.literal_scope.garrison.BuildingDamageEmptyScope" + scope_raw_api_object = RawAPIObject(garrison_scope_ref_in_modpack, + "BuildingDamageEmptyScope", + api_objects) + scope_location = ForwardRef(pregen_converter_group, garrison_literal_ref_in_modpack) + scope_raw_api_object.set_location(scope_location) + scope_raw_api_object.add_raw_parent(SELF_SCOPE_PARENT) + + scope_diplomatic_stances = [api_objects["engine.util.diplomatic_stance.type.Self"]] + scope_raw_api_object.add_raw_member("stances", + scope_diplomatic_stances, + SCOPE_PARENT) + + pregen_converter_group.add_raw_api_object(scope_raw_api_object) + pregen_nyan_objects.update({garrison_scope_ref_in_modpack: scope_raw_api_object}) diff --git a/openage/convert/processor/conversion/aoc/pregen/diplomatic.py b/openage/convert/processor/conversion/aoc/pregen/diplomatic.py new file mode 100644 index 0000000000..ec2fc0b37e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/diplomatic.py @@ -0,0 +1,127 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create diplomatic stances for AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +STANCE_PARENT = "engine.util.diplomatic_stance.DiplomaticStance" +STANCE_LOCATION = "data/util/diplomatic_stance/" + + +def generate_diplomatic_stances( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate DiplomaticStance objects. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_enemy_diplomatic_stance(full_data_set, pregen_converter_group) + _generate_neutral_diplomatic_stance(full_data_set, pregen_converter_group) + _generate_friendly_diplomatic_stance(full_data_set, pregen_converter_group) + _generate_gaia_diplomatic_stance(full_data_set, pregen_converter_group) + + +def _generate_enemy_diplomatic_stance( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Enemy diplomatic stance. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + enemy_ref_in_modpack = "util.diplomatic_stance.types.Enemy" + enemy_raw_api_object = RawAPIObject(enemy_ref_in_modpack, + "Enemy", api_objects, + STANCE_LOCATION) + enemy_raw_api_object.set_filename("types") + enemy_raw_api_object.add_raw_parent(STANCE_PARENT) + + pregen_converter_group.add_raw_api_object(enemy_raw_api_object) + pregen_nyan_objects.update({enemy_ref_in_modpack: enemy_raw_api_object}) + + +def _generate_neutral_diplomatic_stance( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Neutral diplomatic stance. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + neutral_ref_in_modpack = "util.diplomatic_stance.types.Neutral" + neutral_raw_api_object = RawAPIObject(neutral_ref_in_modpack, + "Neutral", api_objects, + STANCE_LOCATION) + neutral_raw_api_object.set_filename("types") + neutral_raw_api_object.add_raw_parent(STANCE_PARENT) + + pregen_converter_group.add_raw_api_object(neutral_raw_api_object) + pregen_nyan_objects.update({neutral_ref_in_modpack: neutral_raw_api_object}) + + +def _generate_friendly_diplomatic_stance( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Friendly diplomatic stance. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + friendly_ref_in_modpack = "util.diplomatic_stance.types.Friendly" + friendly_raw_api_object = RawAPIObject(friendly_ref_in_modpack, + "Friendly", api_objects, + STANCE_LOCATION) + friendly_raw_api_object.set_filename("types") + friendly_raw_api_object.add_raw_parent(STANCE_PARENT) + + pregen_converter_group.add_raw_api_object(friendly_raw_api_object) + pregen_nyan_objects.update({friendly_ref_in_modpack: friendly_raw_api_object}) + + +def _generate_gaia_diplomatic_stance( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Gaia diplomatic stance. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + gaia_ref_in_modpack = "util.diplomatic_stance.types.Gaia" + gaia_raw_api_object = RawAPIObject(gaia_ref_in_modpack, + "Gaia", api_objects, + STANCE_LOCATION) + gaia_raw_api_object.set_filename("types") + gaia_raw_api_object.add_raw_parent(STANCE_PARENT) + + pregen_converter_group.add_raw_api_object(gaia_raw_api_object) + pregen_nyan_objects.update({gaia_ref_in_modpack: gaia_raw_api_object}) diff --git a/openage/convert/processor/conversion/aoc/pregen/effect.py b/openage/convert/processor/conversion/aoc/pregen/effect.py new file mode 100644 index 0000000000..b5abae1695 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/effect.py @@ -0,0 +1,579 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create effect objects for AoC. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +ATTRIBUTE_CHANGE_PARENT = "engine.util.attribute_change_type.AttributeChangeType" +CONSTRUCT_PARENT = "engine.util.progress_type.type.Construct" +CONVERT_PARENT = "engine.util.convert_type.ConvertType" +MIN_CHANGE_AMOUNT_PARENT = "engine.util.attribute.AttributeAmount" +MIN_CHANGE_RATE_PARENT = "engine.util.attribute.AttributeRate" +DISCRETE_FLAC_EFFECT_PARENT = "engine.effect.discrete.flat_attribute_change.FlatAttributeChange" +FALLBACK_EFFECT_PARENT = ( + "engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" +) +DISCRETE_FLAC_RESIST_PARENT = "engine.resistance.discrete.flat_attribute_change.FlatAttributeChange" +FALLBACK_RESIST_PARENT = ( + "engine.resistance.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" +) +ATTRIBUTE_CHANGE_LOCATION = "data/util/attribute_change_type/" +CONSTRUCT_LOCATION = "data/util/construct_type/" +CONVERT_LOCATION = "data/util/convert_type/" +MIN_CHANGE_LOCATION = "data/effect/discrete/flat_attribute_change/" +FALLBACK_EFFECT_LOCATION = "data/effect/discrete/flat_attribute_change/" +FALLBACK_RESIST_LOCATION = "data/resistance/discrete/flat_attribute_change/" + + +def generate_effect_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate types for effects and resistances. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_attribute_change_types(full_data_set, pregen_converter_group) + _generate_construct_types(full_data_set, pregen_converter_group) + _generate_convert_types(full_data_set, pregen_converter_group) + + +def generate_misc_effect_objects( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate fallback types and other standard objects for effects and resistances. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_min_change_values(full_data_set, pregen_converter_group) + _generate_fallback_effects(full_data_set, pregen_converter_group) + _generate_fallback_resistances(full_data_set, pregen_converter_group) + _generate_construct_property(full_data_set, pregen_converter_group) + _generate_repair_property(full_data_set, pregen_converter_group) + + +def _generate_attribute_change_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the attribute change types for effects and resistances from + the armor class lookups. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(full_data_set.game_version) + armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(full_data_set.game_version) + + # ======================================================================= + # Armor classes + # ======================================================================= + + for type_name in armor_lookup_dict.values(): + type_ref_in_modpack = f"util.attribute_change_type.types.{type_name}" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + type_name, api_objects, + ATTRIBUTE_CHANGE_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(ATTRIBUTE_CHANGE_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + # ======================================================================= + # Heal + # ======================================================================= + type_ref_in_modpack = "util.attribute_change_type.types.Heal" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + "Heal", api_objects, + ATTRIBUTE_CHANGE_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(ATTRIBUTE_CHANGE_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + # ======================================================================= + # Repair (one for each repairable entity) + # ======================================================================= + repairable_lines = [] + repairable_lines.extend(full_data_set.building_lines.values()) + for unit_line in full_data_set.unit_lines.values(): + if unit_line.is_repairable(): + repairable_lines.append(unit_line) + + for repairable_line in repairable_lines: + game_entity_name = name_lookup_dict[repairable_line.get_head_unit_id()][0] + + type_ref_in_modpack = f"util.attribute_change_type.types.{game_entity_name}Repair" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + f"{game_entity_name}Repair", + api_objects, + ATTRIBUTE_CHANGE_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(ATTRIBUTE_CHANGE_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + # ======================================================================= + # Construct (HP changes when constructing a building) + # ======================================================================= + constructable_lines = [] + constructable_lines.extend(full_data_set.building_lines.values()) + + for constructable_line in constructable_lines: + game_entity_name = name_lookup_dict[constructable_line.get_head_unit_id()][0] + + type_ref_in_modpack = f"util.attribute_change_type.types.{game_entity_name}Construct" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + f"{game_entity_name}Construct", + api_objects, + ATTRIBUTE_CHANGE_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(ATTRIBUTE_CHANGE_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + +def _generate_construct_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the construct types for AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(full_data_set.game_version) + + constructable_lines = [] + constructable_lines.extend(full_data_set.building_lines.values()) + + for constructable_line in constructable_lines: + game_entity_name = name_lookup_dict[constructable_line.get_head_unit_id()][0] + + type_ref_in_modpack = f"util.construct_type.types.{game_entity_name}Construct" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + f"{game_entity_name}Construct", + api_objects, + CONSTRUCT_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(CONSTRUCT_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + +def _generate_convert_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the convert types for AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Unit conversion + # ======================================================================= + + type_ref_in_modpack = "util.convert_type.types.UnitConvert" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + "UnitConvert", api_objects, + CONVERT_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(CONVERT_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + # ======================================================================= + # Building conversion + # ======================================================================= + type_ref_in_modpack = "util.convert_type.types.BuildingConvert" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + "BuildingConvert", api_objects, + CONVERT_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(CONVERT_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + +def _generate_min_change_values( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the minimum change values for effects. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Min change value (lower cealing for attack effects) + # ======================================================================= + change_ref_in_modpack = "effect.discrete.flat_attribute_change.min_damage.AoE2MinChangeAmount" + change_raw_api_object = RawAPIObject(change_ref_in_modpack, + "AoE2MinChangeAmount", + api_objects, + MIN_CHANGE_LOCATION) + change_raw_api_object.set_filename("min_damage") + change_raw_api_object.add_raw_parent(MIN_CHANGE_AMOUNT_PARENT) + + attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") + change_raw_api_object.add_raw_member("type", + attribute, + MIN_CHANGE_AMOUNT_PARENT) + change_raw_api_object.add_raw_member("amount", + 0, + MIN_CHANGE_AMOUNT_PARENT) + + pregen_converter_group.add_raw_api_object(change_raw_api_object) + pregen_nyan_objects.update({change_ref_in_modpack: change_raw_api_object}) + + # ======================================================================= + # Min change value (lower cealing for heal effects) + # ======================================================================= + change_ref_in_modpack = "effect.discrete.flat_attribute_change.min_heal.AoE2MinChangeAmount" + change_raw_api_object = RawAPIObject(change_ref_in_modpack, + "AoE2MinChangeAmount", + api_objects, + MIN_CHANGE_LOCATION) + change_raw_api_object.set_filename("min_heal") + change_raw_api_object.add_raw_parent(MIN_CHANGE_RATE_PARENT) + + attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") + change_raw_api_object.add_raw_member("type", + attribute, + MIN_CHANGE_RATE_PARENT) + change_raw_api_object.add_raw_member("rate", + 0, + MIN_CHANGE_RATE_PARENT) + + pregen_converter_group.add_raw_api_object(change_raw_api_object) + pregen_nyan_objects.update({change_ref_in_modpack: change_raw_api_object}) + + +def _generate_fallback_effects( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate fallback effects ( = minimum damage) for AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + fallback_ref_in_modpack = "effect.discrete.flat_attribute_change.fallback.AoE2AttackFallback" + fallback_raw_api_object = RawAPIObject(fallback_ref_in_modpack, + "AoE2AttackFallback", + api_objects, + FALLBACK_EFFECT_LOCATION) + fallback_raw_api_object.set_filename("fallback") + fallback_raw_api_object.add_raw_parent(FALLBACK_EFFECT_PARENT) + + # Type + type_ref = "engine.util.attribute_change_type.type.Fallback" + change_type = api_objects[type_ref] + fallback_raw_api_object.add_raw_member("type", + change_type, + DISCRETE_FLAC_EFFECT_PARENT) + + # Min value (optional) + # ================================================================================= + amount_name = f"{fallback_ref_in_modpack}.LowerCealing" + amount_raw_api_object = RawAPIObject(amount_name, "LowerCealing", api_objects) + amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") + amount_location = ForwardRef(pregen_converter_group, fallback_ref_in_modpack) + amount_raw_api_object.set_location(amount_location) + + attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") + amount_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeAmount") + amount_raw_api_object.add_raw_member("amount", + 1, + "engine.util.attribute.AttributeAmount") + + pregen_converter_group.add_raw_api_object(amount_raw_api_object) + pregen_nyan_objects.update({amount_name: amount_raw_api_object}) + # ================================================================================= + amount_forward_ref = ForwardRef(pregen_converter_group, amount_name) + fallback_raw_api_object.add_raw_member("min_change_value", + amount_forward_ref, + DISCRETE_FLAC_EFFECT_PARENT) + + # Max value (optional; not needed + + # Change value + # ================================================================================= + amount_name = f"{fallback_ref_in_modpack}.ChangeAmount" + amount_raw_api_object = RawAPIObject(amount_name, "ChangeAmount", api_objects) + amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") + amount_location = ForwardRef(pregen_converter_group, fallback_ref_in_modpack) + amount_raw_api_object.set_location(amount_location) + + attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") + amount_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeAmount") + amount_raw_api_object.add_raw_member("amount", + 1, + "engine.util.attribute.AttributeAmount") + + pregen_converter_group.add_raw_api_object(amount_raw_api_object) + pregen_nyan_objects.update({amount_name: amount_raw_api_object}) + + # ================================================================================= + amount_forward_ref = ForwardRef(pregen_converter_group, amount_name) + fallback_raw_api_object.add_raw_member("change_value", + amount_forward_ref, + DISCRETE_FLAC_EFFECT_PARENT) + + # Ignore protection + fallback_raw_api_object.add_raw_member("ignore_protection", + [], + DISCRETE_FLAC_EFFECT_PARENT) + + pregen_converter_group.add_raw_api_object(fallback_raw_api_object) + pregen_nyan_objects.update({fallback_ref_in_modpack: fallback_raw_api_object}) + + +def _generate_fallback_resistances( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate fallback resistances for AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + fallback_ref_in_modpack = ( + "resistance.discrete.flat_attribute_change.fallback.AoE2AttackFallback" + ) + fallback_raw_api_object = RawAPIObject(fallback_ref_in_modpack, + "AoE2AttackFallback", + api_objects, + FALLBACK_RESIST_LOCATION) + fallback_raw_api_object.set_filename("fallback") + fallback_raw_api_object.add_raw_parent(FALLBACK_RESIST_PARENT) + + # Type + type_ref = "engine.util.attribute_change_type.type.Fallback" + change_type = api_objects[type_ref] + fallback_raw_api_object.add_raw_member("type", + change_type, + DISCRETE_FLAC_RESIST_PARENT) + + # Block value + # ================================================================================= + amount_name = f"{fallback_ref_in_modpack}.BlockAmount" + amount_raw_api_object = RawAPIObject(amount_name, "BlockAmount", api_objects) + amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") + amount_location = ForwardRef(pregen_converter_group, fallback_ref_in_modpack) + amount_raw_api_object.set_location(amount_location) + + attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") + amount_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeAmount") + amount_raw_api_object.add_raw_member("amount", + 0, + "engine.util.attribute.AttributeAmount") + + pregen_converter_group.add_raw_api_object(amount_raw_api_object) + pregen_nyan_objects.update({amount_name: amount_raw_api_object}) + + # ================================================================================= + amount_forward_ref = ForwardRef(pregen_converter_group, amount_name) + fallback_raw_api_object.add_raw_member("block_value", + amount_forward_ref, + DISCRETE_FLAC_RESIST_PARENT) + + pregen_converter_group.add_raw_api_object(fallback_raw_api_object) + pregen_nyan_objects.update({fallback_ref_in_modpack: fallback_raw_api_object}) + + +def _generate_construct_property( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the construct property for AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + prop_ref_in_modpack = "resistance.property.types.BuildingConstruct" + prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, + "BuildingConstruct", + api_objects, + "data/resistance/property/") + prop_raw_api_object.set_filename("types") + prop_raw_api_object.add_raw_parent("engine.resistance.property.type.Stacked") + + pregen_converter_group.add_raw_api_object(prop_raw_api_object) + pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) + + prop_raw_api_object.add_raw_member("stack_limit", + MemberSpecialValue.NYAN_INF, + "engine.resistance.property.type.Stacked") + + prop_raw_api_object.add_raw_member("distribution_type", + api_objects["engine.util.distribution_type.type.Mean"], + "engine.resistance.property.type.Stacked") + + # ======================================================================= + # Calculation type Construct + # ======================================================================= + calc_parent = "engine.util.calculation_type.type.Hyperbolic" + + calc_ref_in_modpack = "util.calculation_type.construct_calculation.ConstructCalcType" + calc_raw_api_object = RawAPIObject(calc_ref_in_modpack, + "BuildingConstruct", + api_objects) + calc_location = ForwardRef(pregen_converter_group, prop_ref_in_modpack) + calc_raw_api_object.set_location(calc_location) + calc_raw_api_object.add_raw_parent(calc_parent) + + pregen_converter_group.add_raw_api_object(calc_raw_api_object) + pregen_nyan_objects.update({calc_ref_in_modpack: calc_raw_api_object}) + + # Formula: (scale_factor / (count_effectors - shift_x)) + shift_y + # AoE2: (3 / (vil_count + 2)) + + # Shift x + calc_raw_api_object.add_raw_member("shift_x", + -2, + calc_parent) + + # Shift y + calc_raw_api_object.add_raw_member("shift_y", + 0, + calc_parent) + + # Scale + calc_raw_api_object.add_raw_member("scale_factor", + 3, + calc_parent) + + calc_forward_ref = ForwardRef(pregen_converter_group, calc_ref_in_modpack) + prop_raw_api_object.add_raw_member("calculation_type", + calc_forward_ref, + "engine.resistance.property.type.Stacked") + + +def _generate_repair_property( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the repair property for AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + prop_ref_in_modpack = "resistance.property.types.BuildingRepair" + prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, + "BuildingRepair", + api_objects, + "data/resistance/property/") + prop_raw_api_object.set_filename("types") + prop_raw_api_object.add_raw_parent("engine.resistance.property.type.Stacked") + + pregen_converter_group.add_raw_api_object(prop_raw_api_object) + pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) + + prop_raw_api_object.add_raw_member("stack_limit", + MemberSpecialValue.NYAN_INF, + "engine.resistance.property.type.Stacked") + + prop_raw_api_object.add_raw_member("distribution_type", + api_objects["engine.util.distribution_type.type.Mean"], + "engine.resistance.property.type.Stacked") + + # ======================================================================= + # Calculation type Repair + # ======================================================================= + calc_parent = "engine.util.calculation_type.type.Linear" + + calc_ref_in_modpack = "util.calculation_type.construct_calculation.BuildingRepair" + calc_raw_api_object = RawAPIObject(calc_ref_in_modpack, + "BuildingRepair", + api_objects) + calc_location = ForwardRef(pregen_converter_group, prop_ref_in_modpack) + calc_raw_api_object.set_location(calc_location) + calc_raw_api_object.add_raw_parent(calc_parent) + + pregen_converter_group.add_raw_api_object(calc_raw_api_object) + pregen_nyan_objects.update({calc_ref_in_modpack: calc_raw_api_object}) + + # Formula: (scale_factor * (count_effectors - shift_x)) + shift_y + # AoE2: (0.333334 * (vil_count + 2)) + + # Shift x + calc_raw_api_object.add_raw_member("shift_x", + -2, + calc_parent) + + # Shift y + calc_raw_api_object.add_raw_member("shift_y", + 0, + calc_parent) + + # Scale + calc_raw_api_object.add_raw_member("scale_factor", + 1 / 3, + calc_parent) + + calc_forward_ref = ForwardRef(pregen_converter_group, calc_ref_in_modpack) + prop_raw_api_object.add_raw_member("calculation_type", + calc_forward_ref, + "engine.resistance.property.type.Stacked") diff --git a/openage/convert/processor/conversion/aoc/pregen/entity.py b/openage/convert/processor/conversion/aoc/pregen/entity.py new file mode 100644 index 0000000000..f85b91076e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/entity.py @@ -0,0 +1,216 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create entity types for AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....service.conversion import internal_name_lookups + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +TYPE_PARENT = "engine.util.game_entity_type.GameEntityType" +TYPES_LOCATION = "data/util/game_entity_type/" + + +def generate_entity_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate GameEntityType objects. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_ambient_entity_type(full_data_set, pregen_converter_group) + _generate_building_entity_type(full_data_set, pregen_converter_group) + _generate_item_entity_type(full_data_set, pregen_converter_group) + _generate_unit_entity_type(full_data_set, pregen_converter_group) + _generate_projectile_entity_type(full_data_set, pregen_converter_group) + _generate_drop_site_entity_type(full_data_set, pregen_converter_group) + _generate_class_entity_types(full_data_set, pregen_converter_group) + + +def _generate_ambient_entity_type( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Ambient GameEntityType. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + ambient_ref_in_modpack = "util.game_entity_type.types.Ambient" + ambient_raw_api_object = RawAPIObject(ambient_ref_in_modpack, + "Ambient", api_objects, + TYPES_LOCATION) + ambient_raw_api_object.set_filename("types") + ambient_raw_api_object.add_raw_parent(TYPE_PARENT) + + pregen_converter_group.add_raw_api_object(ambient_raw_api_object) + pregen_nyan_objects.update({ambient_ref_in_modpack: ambient_raw_api_object}) + + +def _generate_building_entity_type( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Building GameEntityType. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + building_ref_in_modpack = "util.game_entity_type.types.Building" + building_raw_api_object = RawAPIObject(building_ref_in_modpack, + "Building", api_objects, + TYPES_LOCATION) + building_raw_api_object.set_filename("types") + building_raw_api_object.add_raw_parent(TYPE_PARENT) + + pregen_converter_group.add_raw_api_object(building_raw_api_object) + pregen_nyan_objects.update({building_ref_in_modpack: building_raw_api_object}) + + +def _generate_item_entity_type( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Item GameEntityType. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + item_ref_in_modpack = "util.game_entity_type.types.Item" + item_raw_api_object = RawAPIObject(item_ref_in_modpack, + "Item", api_objects, + TYPES_LOCATION) + item_raw_api_object.set_filename("types") + item_raw_api_object.add_raw_parent(TYPE_PARENT) + + pregen_converter_group.add_raw_api_object(item_raw_api_object) + pregen_nyan_objects.update({item_ref_in_modpack: item_raw_api_object}) + + +def _generate_unit_entity_type( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Unit GameEntityType. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + unit_ref_in_modpack = "util.game_entity_type.types.Unit" + unit_raw_api_object = RawAPIObject(unit_ref_in_modpack, + "Unit", api_objects, + TYPES_LOCATION) + unit_raw_api_object.set_filename("types") + unit_raw_api_object.add_raw_parent(TYPE_PARENT) + + pregen_converter_group.add_raw_api_object(unit_raw_api_object) + pregen_nyan_objects.update({unit_ref_in_modpack: unit_raw_api_object}) + + +def _generate_projectile_entity_type( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the Projectile GameEntityType. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + projectile_ref_in_modpack = "util.game_entity_type.types.Projectile" + projectile_raw_api_object = RawAPIObject(projectile_ref_in_modpack, + "Projectile", api_objects, + TYPES_LOCATION) + projectile_raw_api_object.set_filename("types") + projectile_raw_api_object.add_raw_parent(TYPE_PARENT) + + pregen_converter_group.add_raw_api_object(projectile_raw_api_object) + pregen_nyan_objects.update({projectile_ref_in_modpack: projectile_raw_api_object}) + + +def _generate_drop_site_entity_type( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the DropSite GameEntityType. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + drop_site_ref_in_modpack = "util.game_entity_type.types.DropSite" + drop_site_raw_api_object = RawAPIObject(drop_site_ref_in_modpack, + "DropSite", api_objects, + TYPES_LOCATION) + drop_site_raw_api_object.set_filename("types") + drop_site_raw_api_object.add_raw_parent(TYPE_PARENT) + + pregen_converter_group.add_raw_api_object(drop_site_raw_api_object) + pregen_nyan_objects.update({drop_site_ref_in_modpack: drop_site_raw_api_object}) + + +def _generate_class_entity_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate GameEntityType objects generated from class IDs. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + class_lookup_dict = internal_name_lookups.get_class_lookups(full_data_set.game_version) + + converter_groups = [] + converter_groups.extend(full_data_set.unit_lines.values()) + converter_groups.extend(full_data_set.building_lines.values()) + converter_groups.extend(full_data_set.ambient_groups.values()) + converter_groups.extend(full_data_set.variant_groups.values()) + + for unit_line in converter_groups: + unit_class = unit_line.get_class_id() + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + + new_game_entity_type = RawAPIObject(class_obj_name, class_name, + api_objects, + TYPES_LOCATION) + new_game_entity_type.set_filename("types") + new_game_entity_type.add_raw_parent("engine.util.game_entity_type.GameEntityType") + new_game_entity_type.create_nyan_object() + + pregen_converter_group.add_raw_api_object(new_game_entity_type) + pregen_nyan_objects.update({class_obj_name: new_game_entity_type}) diff --git a/openage/convert/processor/conversion/aoc/pregen/exchange.py b/openage/convert/processor/conversion/aoc/pregen/exchange.py new file mode 100644 index 0000000000..07076aabbb --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/exchange.py @@ -0,0 +1,347 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create exchange objects for trading in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +BUY_MODE_PARENT = "engine.util.exchange_mode.type.Buy" +SELL_MODE_PARENT = "engine.util.exchange_mode.type.Sell" +EXCHANGE_RATE_PARENT = "engine.util.exchange_rate.ExchangeRate" +PRICE_POOL_PARENT = "engine.util.price_pool.PricePool" +PRICE_MODE_PARENT = "engine.util.price_mode.type.Dynamic" +EXCHANGE_OBJECTS_LOCATION = "data/util/resource/" + + +def generate_exchange_objects( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate objects for market trading (ExchangeResources). + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_exchange_modes(full_data_set, pregen_converter_group) + _generate_exchange_rates(full_data_set, pregen_converter_group) + _generate_price_pools(full_data_set, pregen_converter_group) + _generate_price_modes(full_data_set, pregen_converter_group) + + +def _generate_exchange_modes( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate exchange modes for trading in AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Exchange mode Buy + # ======================================================================= + exchange_mode_ref_in_modpack = "util.resource.market_trading.MarketBuyExchangeMode" + exchange_mode_raw_api_object = RawAPIObject(exchange_mode_ref_in_modpack, + "MarketBuyExchangePool", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_mode_raw_api_object.set_filename("market_trading") + exchange_mode_raw_api_object.add_raw_parent(BUY_MODE_PARENT) + + # Fee (30% on top) + exchange_mode_raw_api_object.add_raw_member("fee_multiplier", + 1.3, + "engine.util.exchange_mode.ExchangeMode") + + pregen_converter_group.add_raw_api_object(exchange_mode_raw_api_object) + pregen_nyan_objects.update({exchange_mode_ref_in_modpack: exchange_mode_raw_api_object}) + + # ======================================================================= + # Exchange mode Sell + # ======================================================================= + exchange_mode_ref_in_modpack = "util.resource.market_trading.MarketSellExchangeMode" + exchange_mode_raw_api_object = RawAPIObject(exchange_mode_ref_in_modpack, + "MarketSellExchangeMode", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_mode_raw_api_object.set_filename("market_trading") + exchange_mode_raw_api_object.add_raw_parent(SELL_MODE_PARENT) + + # Fee (30% reduced) + exchange_mode_raw_api_object.add_raw_member("fee_multiplier", + 0.7, + "engine.util.exchange_mode.ExchangeMode") + + pregen_converter_group.add_raw_api_object(exchange_mode_raw_api_object) + pregen_nyan_objects.update({exchange_mode_ref_in_modpack: exchange_mode_raw_api_object}) + + +def _generate_exchange_rates( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate exchange rates for trading in AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Exchange rate Food + # ======================================================================= + exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketFoodExchangeRate" + exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, + "MarketFoodExchangeRate", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_rate_raw_api_object.set_filename("market_trading") + exchange_rate_raw_api_object.add_raw_parent(EXCHANGE_RATE_PARENT) + + # Base price + exchange_rate_raw_api_object.add_raw_member("base_price", + 1.0, + EXCHANGE_RATE_PARENT) + + # Price adjust methods + pa_buy_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketBuyPriceMode") + pa_sell_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketSellPriceMode") + price_adjust = { + api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, + api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref + } + exchange_rate_raw_api_object.add_raw_member("price_adjust", + price_adjust, + EXCHANGE_RATE_PARENT) + + # Price pool + pool_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketFoodPricePool") + exchange_rate_raw_api_object.add_raw_member("price_pool", + pool_forward_ref, + EXCHANGE_RATE_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) + pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) + + # ======================================================================= + # Exchange rate Wood + # ======================================================================= + exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketWoodExchangeRate" + exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, + "MarketWoodExchangeRate", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_rate_raw_api_object.set_filename("market_trading") + exchange_rate_raw_api_object.add_raw_parent(EXCHANGE_RATE_PARENT) + + # Base price + exchange_rate_raw_api_object.add_raw_member("base_price", + 1.0, + EXCHANGE_RATE_PARENT) + + # Price adjust methods + pa_buy_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketBuyPriceMode") + pa_sell_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketSellPriceMode") + price_adjust = { + api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, + api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref + } + exchange_rate_raw_api_object.add_raw_member("price_adjust", + price_adjust, + EXCHANGE_RATE_PARENT) + + # Price pool + pool_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketWoodPricePool") + exchange_rate_raw_api_object.add_raw_member("price_pool", + pool_forward_ref, + EXCHANGE_RATE_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) + pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) + + # ======================================================================= + # Exchange rate Stone + # ======================================================================= + exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketStoneExchangeRate" + exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, + "MarketStoneExchangeRate", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_rate_raw_api_object.set_filename("market_trading") + exchange_rate_raw_api_object.add_raw_parent(EXCHANGE_RATE_PARENT) + + # Base price + exchange_rate_raw_api_object.add_raw_member("base_price", + 1.3, + EXCHANGE_RATE_PARENT) + + # Price adjust methods + pa_buy_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketBuyPriceMode") + pa_sell_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketSellPriceMode") + price_adjust = { + api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, + api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref + } + exchange_rate_raw_api_object.add_raw_member("price_adjust", + price_adjust, + EXCHANGE_RATE_PARENT) + + # Price pool + pool_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketStonePricePool") + exchange_rate_raw_api_object.add_raw_member("price_pool", + pool_forward_ref, + EXCHANGE_RATE_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) + pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) + + +def _generate_price_pools( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate price pools for trading in AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Market Food price pool + # ======================================================================= + exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketFoodPricePool" + exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, + "MarketFoodPricePool", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_pool_raw_api_object.set_filename("market_trading") + exchange_pool_raw_api_object.add_raw_parent(PRICE_POOL_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) + pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) + + # ======================================================================= + # Market Wood price pool + # ======================================================================= + exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketWoodPricePool" + exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, + "MarketWoodPricePool", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_pool_raw_api_object.set_filename("market_trading") + exchange_pool_raw_api_object.add_raw_parent(PRICE_POOL_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) + pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) + + # ======================================================================= + # Market Stone price pool + # ======================================================================= + exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketStonePricePool" + exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, + "MarketStonePricePool", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_pool_raw_api_object.set_filename("market_trading") + exchange_pool_raw_api_object.add_raw_parent(PRICE_POOL_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) + pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) + + +def _generate_price_modes( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate price modes for trading in AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Buy Price mode + # ======================================================================= + price_mode_ref_in_modpack = "util.resource.market_trading.MarketBuyPriceMode" + price_mode_raw_api_object = RawAPIObject(price_mode_ref_in_modpack, + "MarketBuyPriceMode", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + price_mode_raw_api_object.set_filename("market_trading") + price_mode_raw_api_object.add_raw_parent(PRICE_MODE_PARENT) + + # Min price + price_mode_raw_api_object.add_raw_member("change_value", + 0.03, + PRICE_MODE_PARENT) + + # Min price + price_mode_raw_api_object.add_raw_member("min_price", + 0.3, + PRICE_MODE_PARENT) + + # Max price + price_mode_raw_api_object.add_raw_member("max_price", + 99.9, + PRICE_MODE_PARENT) + + pregen_converter_group.add_raw_api_object(price_mode_raw_api_object) + pregen_nyan_objects.update({price_mode_ref_in_modpack: price_mode_raw_api_object}) + + # ======================================================================= + # Sell Price mode + # ======================================================================= + price_mode_ref_in_modpack = "util.resource.market_trading.MarketSellPriceMode" + price_mode_raw_api_object = RawAPIObject(price_mode_ref_in_modpack, + "MarketSellPriceMode", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + price_mode_raw_api_object.set_filename("market_trading") + price_mode_raw_api_object.add_raw_parent(PRICE_MODE_PARENT) + + # Min price + price_mode_raw_api_object.add_raw_member("change_value", + -0.03, + PRICE_MODE_PARENT) + + # Min price + price_mode_raw_api_object.add_raw_member("min_price", + 0.3, + PRICE_MODE_PARENT) + + # Max price + price_mode_raw_api_object.add_raw_member("max_price", + 99.9, + PRICE_MODE_PARENT) + + pregen_converter_group.add_raw_api_object(price_mode_raw_api_object) + pregen_nyan_objects.update({price_mode_ref_in_modpack: price_mode_raw_api_object}) diff --git a/openage/convert/processor/conversion/aoc/pregen/formation.py b/openage/convert/processor/conversion/aoc/pregen/formation.py new file mode 100644 index 0000000000..cce27d5899 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/formation.py @@ -0,0 +1,250 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create formation types for AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +FORMATION_PARENT = "engine.util.formation.Formation" +SUBFORMATION_PARENT = "engine.util.formation.Subformation" +FORMATION_LOCATION = "data/util/formation/" +SUBFORMATION_LOCATION = "data/util/formation/" + + +def generate_formation_objects( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate Formation and Subformation objects. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_formation_types(full_data_set, pregen_converter_group) + _generate_subformation_types(full_data_set, pregen_converter_group) + + +def _generate_formation_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate formation types for AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Line formation + # ======================================================================= + formation_ref_in_modpack = "util.formation.types.Line" + formation_raw_api_object = RawAPIObject(formation_ref_in_modpack, + "Line", + api_objects, + FORMATION_LOCATION) + formation_raw_api_object.set_filename("types") + formation_raw_api_object.add_raw_parent(FORMATION_PARENT) + + subformations = [ + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Cavalry"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Infantry"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Ranged"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Siege"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Support"), + ] + formation_raw_api_object.add_raw_member("subformations", + subformations, + FORMATION_PARENT) + + pregen_converter_group.add_raw_api_object(formation_raw_api_object) + pregen_nyan_objects.update({formation_ref_in_modpack: formation_raw_api_object}) + + # ======================================================================= + # Staggered formation + # ======================================================================= + formation_ref_in_modpack = "util.formation.types.Staggered" + formation_raw_api_object = RawAPIObject(formation_ref_in_modpack, + "Staggered", + api_objects, + FORMATION_LOCATION) + formation_raw_api_object.set_filename("types") + formation_raw_api_object.add_raw_parent(FORMATION_PARENT) + + subformations = [ + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Cavalry"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Infantry"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Ranged"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Siege"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Support"), + ] + formation_raw_api_object.add_raw_member("subformations", + subformations, + FORMATION_PARENT) + + pregen_converter_group.add_raw_api_object(formation_raw_api_object) + pregen_nyan_objects.update({formation_ref_in_modpack: formation_raw_api_object}) + + # ======================================================================= + # Box formation + # ======================================================================= + formation_ref_in_modpack = "util.formation.types.Box" + formation_raw_api_object = RawAPIObject(formation_ref_in_modpack, + "Box", + api_objects, + FORMATION_LOCATION) + formation_raw_api_object.set_filename("types") + formation_raw_api_object.add_raw_parent(FORMATION_PARENT) + + subformations = [ + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Cavalry"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Infantry"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Ranged"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Siege"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Support"), + ] + formation_raw_api_object.add_raw_member("subformations", + subformations, + FORMATION_PARENT) + + pregen_converter_group.add_raw_api_object(formation_raw_api_object) + pregen_nyan_objects.update({formation_ref_in_modpack: formation_raw_api_object}) + + # ======================================================================= + # Flank formation + # ======================================================================= + formation_ref_in_modpack = "util.formation.types.Flank" + formation_raw_api_object = RawAPIObject(formation_ref_in_modpack, + "Flank", + api_objects, + FORMATION_LOCATION) + formation_raw_api_object.set_filename("types") + formation_raw_api_object.add_raw_parent(FORMATION_PARENT) + + subformations = [ + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Cavalry"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Infantry"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Ranged"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Siege"), + ForwardRef(pregen_converter_group, "util.formation.subformation.types.Support"), + ] + formation_raw_api_object.add_raw_member("subformations", + subformations, + FORMATION_PARENT) + + pregen_converter_group.add_raw_api_object(formation_raw_api_object) + pregen_nyan_objects.update({formation_ref_in_modpack: formation_raw_api_object}) + + +def _generate_subformation_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate subformation types for AoC. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Cavalry subformation + # ======================================================================= + subformation_ref_in_modpack = "util.formation.subformation.types.Cavalry" + subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, + "Cavalry", + api_objects, + SUBFORMATION_LOCATION) + subformation_raw_api_object.set_filename("subformations") + subformation_raw_api_object.add_raw_parent(SUBFORMATION_PARENT) + + subformation_raw_api_object.add_raw_member("ordering_priority", + 5, + SUBFORMATION_PARENT) + + pregen_converter_group.add_raw_api_object(subformation_raw_api_object) + pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) + + # ======================================================================= + # Infantry subformation + # ======================================================================= + subformation_ref_in_modpack = "util.formation.subformation.types.Infantry" + subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, + "Infantry", + api_objects, + SUBFORMATION_LOCATION) + subformation_raw_api_object.set_filename("subformations") + subformation_raw_api_object.add_raw_parent(SUBFORMATION_PARENT) + + subformation_raw_api_object.add_raw_member("ordering_priority", + 4, + SUBFORMATION_PARENT) + + pregen_converter_group.add_raw_api_object(subformation_raw_api_object) + pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) + + # ======================================================================= + # Ranged subformation + # ======================================================================= + subformation_ref_in_modpack = "util.formation.subformation.types.Ranged" + subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, + "Ranged", + api_objects, + SUBFORMATION_LOCATION) + subformation_raw_api_object.set_filename("subformations") + subformation_raw_api_object.add_raw_parent(SUBFORMATION_PARENT) + + subformation_raw_api_object.add_raw_member("ordering_priority", + 3, + SUBFORMATION_PARENT) + + pregen_converter_group.add_raw_api_object(subformation_raw_api_object) + pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) + + # ======================================================================= + # Siege subformation + # ======================================================================= + subformation_ref_in_modpack = "util.formation.subformation.types.Siege" + subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, + "Siege", + api_objects, + SUBFORMATION_LOCATION) + subformation_raw_api_object.set_filename("subformations") + subformation_raw_api_object.add_raw_parent(SUBFORMATION_PARENT) + + subformation_raw_api_object.add_raw_member("ordering_priority", + 2, + SUBFORMATION_PARENT) + + pregen_converter_group.add_raw_api_object(subformation_raw_api_object) + pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) + + # ======================================================================= + # Support subformation + # ======================================================================= + subformation_ref_in_modpack = "util.formation.subformation.types.Support" + subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, + "Support", + api_objects, + SUBFORMATION_LOCATION) + subformation_raw_api_object.set_filename("subformations") + subformation_raw_api_object.add_raw_parent(SUBFORMATION_PARENT) + + subformation_raw_api_object.add_raw_member("ordering_priority", + 1, + SUBFORMATION_PARENT) + + pregen_converter_group.add_raw_api_object(subformation_raw_api_object) + pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) diff --git a/openage/convert/processor/conversion/aoc/pregen/language.py b/openage/convert/processor/conversion/aoc/pregen/language.py new file mode 100644 index 0000000000..8894b780b9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/language.py @@ -0,0 +1,47 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create language objects for AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +LANGUAGE_PARENT = "engine.util.language.Language" +LANGUAGE_LOCATION = "data/util/language/" + + +def generate_language_objects( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate language objects from the string resources + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + languages = full_data_set.strings.get_tables().keys() + + for language in languages: + language_ref_in_modpack = f"util.language.{language}" + language_raw_api_object = RawAPIObject(language_ref_in_modpack, + language, + api_objects, + LANGUAGE_LOCATION) + language_raw_api_object.set_filename("language") + language_raw_api_object.add_raw_parent(LANGUAGE_PARENT) + + language_raw_api_object.add_raw_member("ietf_string", + language, + LANGUAGE_PARENT) + + pregen_converter_group.add_raw_api_object(language_raw_api_object) + pregen_nyan_objects.update({language_ref_in_modpack: language_raw_api_object}) diff --git a/openage/convert/processor/conversion/aoc/pregen/misc.py b/openage/convert/processor/conversion/aoc/pregen/misc.py new file mode 100644 index 0000000000..3974d48d85 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/misc.py @@ -0,0 +1 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. diff --git a/openage/convert/processor/conversion/aoc/pregen/modifier.py b/openage/convert/processor/conversion/aoc/pregen/modifier.py new file mode 100644 index 0000000000..48ed6fa8b5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/modifier.py @@ -0,0 +1,189 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create modifiers for AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +MODIFIER_PARENT = "engine.modifier.Modifier" +MPROP_PARENT = "engine.modifier.property.type.Multiplier" +FLYOVER_PARENT = "engine.modifier.effect.flat_attribute_change.type.Flyover" +ELEVATION_DIFF_HIGH_PARENT = ( + "engine.modifier.effect.flat_attribute_change.type.ElevationDifferenceHigh" +) +ELEVATION_DIFF_LOW_PARENT = ( + "engine.modifier.effect.flat_attribute_change.type.ElevationDifferenceLow" +) +ELEVATION_DIFF_LOCATION = "data/util/modifier/elevation_difference/" +FLYOVER_LOCATION = "data/util/modifier/flyover_cliff/" + + +def generate_modifiers( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate standard modifiers. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_flyover_cliff_modifier(full_data_set, pregen_converter_group) + _generate_elevation_difference_modifiers(full_data_set, pregen_converter_group) + + +def _generate_flyover_cliff_modifier( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the flyover cliff modifier. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + modifier_ref_in_modpack = "util.modifier.flyover_cliff.AttackFlyover" + modifier_raw_api_object = RawAPIObject(modifier_ref_in_modpack, + "AttackFlyover", api_objects, + FLYOVER_LOCATION) + modifier_raw_api_object.set_filename("flyover_cliff") + modifier_raw_api_object.add_raw_parent(FLYOVER_PARENT) + + pregen_converter_group.add_raw_api_object(modifier_raw_api_object) + pregen_nyan_objects.update({modifier_ref_in_modpack: modifier_raw_api_object}) + + # Relative angle to cliff must not be smaller than 90° + modifier_raw_api_object.add_raw_member("relative_angle", + 90, + FLYOVER_PARENT) + + # Affects all cliffs + types = [ForwardRef(pregen_converter_group, "util.game_entity_type.types.Cliff")] + modifier_raw_api_object.add_raw_member("flyover_types", + types, + FLYOVER_PARENT) + modifier_raw_api_object.add_raw_member("blacklisted_entities", + [], + FLYOVER_PARENT) + + # Multiplier property: Increases effect value by 25% + # -------------------------------------------------- + prop_ref_in_modpack = "util.modifier.flyover_cliff.AttackFlyover.Multiplier" + prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, + "Multiplier", api_objects, + FLYOVER_LOCATION) + prop_location = ForwardRef(pregen_converter_group, modifier_ref_in_modpack) + prop_raw_api_object.set_location(prop_location) + prop_raw_api_object.add_raw_parent(MPROP_PARENT) + + pregen_converter_group.add_raw_api_object(prop_raw_api_object) + pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) + + prop_raw_api_object.add_raw_member("multiplier", + 1.25, + MPROP_PARENT) + # -------------------------------------------------- + # Assign property to modifier + prop_forward_ref = ForwardRef(pregen_converter_group, prop_ref_in_modpack) + properties = {api_objects[MPROP_PARENT]: prop_forward_ref} + modifier_raw_api_object.add_raw_member("properties", + properties, + MODIFIER_PARENT) + + +def _generate_elevation_difference_modifiers( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate elevation difference modifiers. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Elevation difference effect multiplier (higher unit) + # ======================================================================= + modifier_ref_in_modpack = "util.modifier.elevation_difference.AttackHigh" + modifier_raw_api_object = RawAPIObject(modifier_ref_in_modpack, + "AttackHigh", api_objects, + ELEVATION_DIFF_LOCATION) + modifier_raw_api_object.set_filename("elevation_difference") + modifier_raw_api_object.add_raw_parent(ELEVATION_DIFF_HIGH_PARENT) + + pregen_converter_group.add_raw_api_object(modifier_raw_api_object) + pregen_nyan_objects.update({modifier_ref_in_modpack: modifier_raw_api_object}) + + # Multiplier property: Increases effect value to 125% + # -------------------------------------------------- + prop_ref_in_modpack = "util.modifier.elevation_difference.AttackHigh.Multiplier" + prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, + "Multiplier", api_objects, + ELEVATION_DIFF_LOCATION) + prop_location = ForwardRef(pregen_converter_group, modifier_ref_in_modpack) + prop_raw_api_object.set_location(prop_location) + prop_raw_api_object.add_raw_parent(MPROP_PARENT) + + pregen_converter_group.add_raw_api_object(prop_raw_api_object) + pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) + + prop_raw_api_object.add_raw_member("multiplier", + 1.25, + MPROP_PARENT) + # -------------------------------------------------- + # Assign property to modifier + prop_forward_ref = ForwardRef(pregen_converter_group, prop_ref_in_modpack) + properties = {api_objects[MPROP_PARENT]: prop_forward_ref} + modifier_raw_api_object.add_raw_member("properties", + properties, + MODIFIER_PARENT) + + # ======================================================================= + # Elevation difference effect multiplier (lower unit) + # ======================================================================= + modifier_ref_in_modpack = "util.modifier.elevation_difference.AttackLow" + modifier_raw_api_object = RawAPIObject(modifier_ref_in_modpack, + "AttackLow", api_objects, + ELEVATION_DIFF_LOCATION) + modifier_raw_api_object.set_filename("elevation_difference") + modifier_raw_api_object.add_raw_parent(ELEVATION_DIFF_LOW_PARENT) + + pregen_converter_group.add_raw_api_object(modifier_raw_api_object) + pregen_nyan_objects.update({modifier_ref_in_modpack: modifier_raw_api_object}) + + # Multiplier property: Decreases effect value to 75% + # -------------------------------------------------- + prop_ref_in_modpack = "util.modifier.elevation_difference.AttackLow.Multiplier" + prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, + "Multiplier", api_objects, + ELEVATION_DIFF_LOCATION) + prop_location = ForwardRef(pregen_converter_group, modifier_ref_in_modpack) + prop_raw_api_object.set_location(prop_location) + prop_raw_api_object.add_raw_parent(MPROP_PARENT) + + pregen_converter_group.add_raw_api_object(prop_raw_api_object) + pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) + + prop_raw_api_object.add_raw_member("multiplier", + 1.25, + MPROP_PARENT) + # -------------------------------------------------- + # Assign property to modifier + prop_forward_ref = ForwardRef(pregen_converter_group, prop_ref_in_modpack) + properties = {api_objects[MPROP_PARENT]: prop_forward_ref} + modifier_raw_api_object.add_raw_member("properties", + properties, + MODIFIER_PARENT) diff --git a/openage/convert/processor/conversion/aoc/pregen/path.py b/openage/convert/processor/conversion/aoc/pregen/path.py new file mode 100644 index 0000000000..36f6c40281 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/path.py @@ -0,0 +1,71 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create path types for AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +PATH_TYPE_PARENT = "engine.util.path_type.PathType" +PATH_TYPES_LOCATION = "data/util/path_type/" + + +def generate_path_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate PathType objects. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Land + # ======================================================================= + path_type_ref_in_modpack = "util.path.types.Land" + path_type_raw_api_object = RawAPIObject(path_type_ref_in_modpack, + "Land", + api_objects, + PATH_TYPES_LOCATION) + path_type_raw_api_object.set_filename("types") + path_type_raw_api_object.add_raw_parent(PATH_TYPE_PARENT) + + pregen_converter_group.add_raw_api_object(path_type_raw_api_object) + pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) + + # ======================================================================= + # Water + # ======================================================================= + path_type_ref_in_modpack = "util.path.types.Water" + path_type_raw_api_object = RawAPIObject(path_type_ref_in_modpack, + "Water", + api_objects, + PATH_TYPES_LOCATION) + path_type_raw_api_object.set_filename("types") + path_type_raw_api_object.add_raw_parent(PATH_TYPE_PARENT) + + pregen_converter_group.add_raw_api_object(path_type_raw_api_object) + pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) + + # ======================================================================= + # Air + # ======================================================================= + path_type_ref_in_modpack = "util.path.types.Air" + path_type_raw_api_object = RawAPIObject(path_type_ref_in_modpack, + "Air", + api_objects, + PATH_TYPES_LOCATION) + path_type_raw_api_object.set_filename("types") + path_type_raw_api_object.add_raw_parent(PATH_TYPE_PARENT) + + pregen_converter_group.add_raw_api_object(path_type_raw_api_object) + pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) diff --git a/openage/convert/processor/conversion/aoc/pregen/resource.py b/openage/convert/processor/conversion/aoc/pregen/resource.py new file mode 100644 index 0000000000..88253940b8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/resource.py @@ -0,0 +1,265 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create resources and resource types for AoC. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +RESOURCE_PARENT = "engine.util.resource.Resource" +RESOURCE_CONTINGENT_PARENT = "engine.util.resource.ResourceContingent" +RESOURCES_LOCATION = "data/util/resource/" + + +def generate_resources( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate Resource objects. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_food_resource(full_data_set, pregen_converter_group) + _generate_wood_resource(full_data_set, pregen_converter_group) + _generate_stone_resource(full_data_set, pregen_converter_group) + _generate_gold_resource(full_data_set, pregen_converter_group) + _generate_population_resource(full_data_set, pregen_converter_group) + + +def _generate_food_resource( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the food resource type. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + food_ref_in_modpack = "util.resource.types.Food" + food_raw_api_object = RawAPIObject(food_ref_in_modpack, + "Food", api_objects, + RESOURCES_LOCATION) + food_raw_api_object.set_filename("types") + food_raw_api_object.add_raw_parent(RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(food_raw_api_object) + pregen_nyan_objects.update({food_ref_in_modpack: food_raw_api_object}) + + food_raw_api_object.add_raw_member("max_storage", + MemberSpecialValue.NYAN_INF, + RESOURCE_PARENT) + + name_value_parent = "engine.util.language.translated.type.TranslatedString" + food_name_ref_in_modpack = "util.attribute.types.Food.FoodName" + food_name_value = RawAPIObject(food_name_ref_in_modpack, "FoodName", + api_objects, RESOURCES_LOCATION) + food_name_value.set_filename("types") + food_name_value.add_raw_parent(name_value_parent) + food_name_value.add_raw_member("translations", [], name_value_parent) + + name_forward_ref = ForwardRef(pregen_converter_group, + food_name_ref_in_modpack) + food_raw_api_object.add_raw_member("name", + name_forward_ref, + RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(food_name_value) + pregen_nyan_objects.update({food_name_ref_in_modpack: food_name_value}) + + +def _generate_wood_resource( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the wood resource type. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + wood_ref_in_modpack = "util.resource.types.Wood" + wood_raw_api_object = RawAPIObject(wood_ref_in_modpack, + "Wood", api_objects, + RESOURCES_LOCATION) + wood_raw_api_object.set_filename("types") + wood_raw_api_object.add_raw_parent(RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(wood_raw_api_object) + pregen_nyan_objects.update({wood_ref_in_modpack: wood_raw_api_object}) + + wood_raw_api_object.add_raw_member("max_storage", + MemberSpecialValue.NYAN_INF, + RESOURCE_PARENT) + + name_value_parent = "engine.util.language.translated.type.TranslatedString" + wood_name_ref_in_modpack = "util.attribute.types.Wood.WoodName" + wood_name_value = RawAPIObject(wood_name_ref_in_modpack, "WoodName", + api_objects, RESOURCES_LOCATION) + wood_name_value.set_filename("types") + wood_name_value.add_raw_parent(name_value_parent) + wood_name_value.add_raw_member("translations", [], name_value_parent) + + name_forward_ref = ForwardRef(pregen_converter_group, + wood_name_ref_in_modpack) + wood_raw_api_object.add_raw_member("name", + name_forward_ref, + RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(wood_name_value) + pregen_nyan_objects.update({wood_name_ref_in_modpack: wood_name_value}) + + +def _generate_stone_resource( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the stone resource type. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + stone_ref_in_modpack = "util.resource.types.Stone" + stone_raw_api_object = RawAPIObject(stone_ref_in_modpack, + "Stone", api_objects, + RESOURCES_LOCATION) + stone_raw_api_object.set_filename("types") + stone_raw_api_object.add_raw_parent(RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(stone_raw_api_object) + pregen_nyan_objects.update({stone_ref_in_modpack: stone_raw_api_object}) + + stone_raw_api_object.add_raw_member("max_storage", + MemberSpecialValue.NYAN_INF, + RESOURCE_PARENT) + + name_value_parent = "engine.util.language.translated.type.TranslatedString" + stone_name_ref_in_modpack = "util.attribute.types.Stone.StoneName" + stone_name_value = RawAPIObject(stone_name_ref_in_modpack, "StoneName", + api_objects, RESOURCES_LOCATION) + stone_name_value.set_filename("types") + stone_name_value.add_raw_parent(name_value_parent) + stone_name_value.add_raw_member("translations", [], name_value_parent) + + name_forward_ref = ForwardRef(pregen_converter_group, + stone_name_ref_in_modpack) + stone_raw_api_object.add_raw_member("name", + name_forward_ref, + RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(stone_name_value) + pregen_nyan_objects.update({stone_name_ref_in_modpack: stone_name_value}) + + +def _generate_gold_resource( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the gold resource type. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + gold_ref_in_modpack = "util.resource.types.Gold" + gold_raw_api_object = RawAPIObject(gold_ref_in_modpack, + "Gold", api_objects, + RESOURCES_LOCATION) + gold_raw_api_object.set_filename("types") + gold_raw_api_object.add_raw_parent(RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(gold_raw_api_object) + pregen_nyan_objects.update({gold_ref_in_modpack: gold_raw_api_object}) + + gold_raw_api_object.add_raw_member("max_storage", + MemberSpecialValue.NYAN_INF, + RESOURCE_PARENT) + + name_value_parent = "engine.util.language.translated.type.TranslatedString" + gold_name_ref_in_modpack = "util.attribute.types.Gold.GoldName" + gold_name_value = RawAPIObject(gold_name_ref_in_modpack, "GoldName", + api_objects, RESOURCES_LOCATION) + gold_name_value.set_filename("types") + gold_name_value.add_raw_parent(name_value_parent) + gold_name_value.add_raw_member("translations", [], name_value_parent) + + name_forward_ref = ForwardRef(pregen_converter_group, + gold_name_ref_in_modpack) + gold_raw_api_object.add_raw_member("name", + name_forward_ref, + RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(gold_name_value) + pregen_nyan_objects.update({gold_name_ref_in_modpack: gold_name_value}) + + +def _generate_population_resource( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the population space resource type. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + pop_ref_in_modpack = "util.resource.types.PopulationSpace" + pop_raw_api_object = RawAPIObject(pop_ref_in_modpack, + "PopulationSpace", api_objects, + RESOURCES_LOCATION) + pop_raw_api_object.set_filename("types") + pop_raw_api_object.add_raw_parent(RESOURCE_CONTINGENT_PARENT) + + pregen_converter_group.add_raw_api_object(pop_raw_api_object) + pregen_nyan_objects.update({pop_ref_in_modpack: pop_raw_api_object}) + + name_value_parent = "engine.util.language.translated.type.TranslatedString" + pop_name_ref_in_modpack = "util.attribute.types.PopulationSpace.PopulationSpaceName" + pop_name_value = RawAPIObject(pop_name_ref_in_modpack, "PopulationSpaceName", + api_objects, RESOURCES_LOCATION) + pop_name_value.set_filename("types") + pop_name_value.add_raw_parent(name_value_parent) + pop_name_value.add_raw_member("translations", [], name_value_parent) + + name_forward_ref = ForwardRef(pregen_converter_group, + pop_name_ref_in_modpack) + pop_raw_api_object.add_raw_member("name", + name_forward_ref, + RESOURCE_PARENT) + pop_raw_api_object.add_raw_member("max_storage", + MemberSpecialValue.NYAN_INF, + RESOURCE_PARENT) + pop_raw_api_object.add_raw_member("min_amount", + 0, + RESOURCE_CONTINGENT_PARENT) + pop_raw_api_object.add_raw_member("max_amount", + 200, + RESOURCE_CONTINGENT_PARENT) + + pregen_converter_group.add_raw_api_object(pop_name_value) + pregen_nyan_objects.update({pop_name_ref_in_modpack: pop_name_value}) diff --git a/openage/convert/processor/conversion/aoc/pregen/team_property.py b/openage/convert/processor/conversion/aoc/pregen/team_property.py new file mode 100644 index 0000000000..bdaf3dcee3 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/team_property.py @@ -0,0 +1,52 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create team properties for AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def generate_team_property( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the property used in team patches objects. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer + :param pregen_converter_group: GenieObjectGroup instance that stores + pregenerated API objects for referencing with + ForwardRef + :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + prop_ref_in_modpack = "util.patch.property.types.Team" + prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, + "Team", + api_objects, + "data/util/patch/property/") + prop_raw_api_object.set_filename("types") + prop_raw_api_object.add_raw_parent("engine.util.patch.property.type.Diplomatic") + + pregen_converter_group.add_raw_api_object(prop_raw_api_object) + pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) + + stances = [ + full_data_set.nyan_api_objects["engine.util.diplomatic_stance.type.Self"], + ForwardRef(pregen_converter_group, "util.diplomatic_stance.types.Friendly") + ] + prop_raw_api_object.add_raw_member("stances", + stances, + "engine.util.patch.property.type.Diplomatic") diff --git a/openage/convert/processor/conversion/aoc/pregen/terrain.py b/openage/convert/processor/conversion/aoc/pregen/terrain.py new file mode 100644 index 0000000000..f0a4061bd8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/terrain.py @@ -0,0 +1,47 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create terrain types for AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....service.conversion import internal_name_lookups + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def generate_terrain_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate TerrainType objects. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( + full_data_set.game_version) + + type_parent = "engine.util.terrain_type.TerrainType" + types_location = "data/util/terrain_type/" + + terrain_type_lookups = terrain_type_lookup_dict.values() + + for terrain_type in terrain_type_lookups: + type_name = terrain_type[2] + type_ref_in_modpack = f"util.terrain_type.types.{type_name}" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + type_name, api_objects, + types_location) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(type_parent) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index 963fa22a79..5f590ceed3 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -1,9 +1,4 @@ # Copyright 2020-2025 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-lines,too-many-locals,too-many-statements -# -# TODO: -# pylint: disable=line-too-long """ Creates nyan objects for things that are hardcoded into the Genie Engine, @@ -12,14 +7,24 @@ from __future__ import annotations import typing -from .....nyan.nyan_structs import MemberSpecialValue -from ....entity_object.conversion.converter_object import RawAPIObject, \ - ConverterObjectGroup -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef +from ....entity_object.conversion.converter_object import ConverterObjectGroup +from .pregen.activity import generate_activities +from .pregen.attribute import generate_attributes +from .pregen.diplomatic import generate_diplomatic_stances +from .pregen.condition import generate_death_condition, generate_garrison_empty_condition +from .pregen.resource import generate_resources +from .pregen.team_property import generate_team_property +from .pregen.entity import generate_entity_types +from .pregen.effect import generate_effect_types, generate_misc_effect_objects +from .pregen.exchange import generate_exchange_objects +from .pregen.formation import generate_formation_objects +from .pregen.language import generate_language_objects +from .pregen.modifier import generate_modifiers +from .pregen.terrain import generate_terrain_types +from .pregen.path import generate_path_types if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer class AoCPregenSubprocessor: @@ -35,21 +40,22 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: # Stores pregenerated raw API objects as a container pregen_converter_group = ConverterObjectGroup("pregen") - cls.generate_activities(full_data_set, pregen_converter_group) - cls.generate_attributes(full_data_set, pregen_converter_group) - cls.generate_diplomatic_stances(full_data_set, pregen_converter_group) - cls.generate_team_property(full_data_set, pregen_converter_group) - cls.generate_entity_types(full_data_set, pregen_converter_group) - cls.generate_effect_types(full_data_set, pregen_converter_group) - cls.generate_exchange_objects(full_data_set, pregen_converter_group) - cls.generate_formation_types(full_data_set, pregen_converter_group) - cls.generate_language_objects(full_data_set, pregen_converter_group) - cls.generate_misc_effect_objects(full_data_set, pregen_converter_group) - cls.generate_modifiers(full_data_set, pregen_converter_group) - cls.generate_terrain_types(full_data_set, pregen_converter_group) - cls.generate_path_types(full_data_set, pregen_converter_group) - cls.generate_resources(full_data_set, pregen_converter_group) - cls.generate_death_condition(full_data_set, pregen_converter_group) + generate_activities(full_data_set, pregen_converter_group) + generate_attributes(full_data_set, pregen_converter_group) + generate_diplomatic_stances(full_data_set, pregen_converter_group) + generate_team_property(full_data_set, pregen_converter_group) + generate_entity_types(full_data_set, pregen_converter_group) + generate_effect_types(full_data_set, pregen_converter_group) + generate_exchange_objects(full_data_set, pregen_converter_group) + generate_formation_objects(full_data_set, pregen_converter_group) + generate_language_objects(full_data_set, pregen_converter_group) + generate_misc_effect_objects(full_data_set, pregen_converter_group) + generate_modifiers(full_data_set, pregen_converter_group) + generate_terrain_types(full_data_set, pregen_converter_group) + generate_path_types(full_data_set, pregen_converter_group) + generate_resources(full_data_set, pregen_converter_group) + generate_death_condition(full_data_set, pregen_converter_group) + generate_garrison_empty_condition(full_data_set, pregen_converter_group) pregen_nyan_objects = full_data_set.pregen_nyan_objects # Create nyan objects from the raw API objects @@ -64,2449 +70,19 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: raise RuntimeError(f"{repr(pregen_object)}: Pregenerated object is not ready " "for export. Member or object not initialized.") - @staticmethod - def generate_activities( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate the activities for game entity behaviour. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - activity_parent = "engine.util.activity.Activity" - activity_location = "data/util/activity/" - - # Node types - 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" - - # 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 = ( - "engine.util.activity.switch_condition.type.NextCommand" - ) - - # ======================================================================= - # Default (Start -> Ability(Idle) -> End) - # ======================================================================= - default_ref_in_modpack = "util.activity.types.Default" - default_raw_api_object = RawAPIObject(default_ref_in_modpack, - "Default", api_objects, - activity_location) - default_raw_api_object.set_filename("types") - default_raw_api_object.add_raw_parent(activity_parent) - - start_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Default.Start") - default_raw_api_object.add_raw_member("start", start_forward_ref, - activity_parent) - - pregen_converter_group.add_raw_api_object(default_raw_api_object) - pregen_nyan_objects.update({default_ref_in_modpack: default_raw_api_object}) - - unit_forward_ref = ForwardRef(pregen_converter_group, default_ref_in_modpack) - - # Start - start_ref_in_modpack = "util.activity.types.Default.Start" - start_raw_api_object = RawAPIObject(start_ref_in_modpack, - "Start", api_objects) - start_raw_api_object.set_location(unit_forward_ref) - start_raw_api_object.add_raw_parent(start_parent) - - idle_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Default.Idle") - start_raw_api_object.add_raw_member("next", idle_forward_ref, - start_parent) - - pregen_converter_group.add_raw_api_object(start_raw_api_object) - pregen_nyan_objects.update({start_ref_in_modpack: start_raw_api_object}) - - # Idle - idle_ref_in_modpack = "util.activity.types.Default.Idle" - idle_raw_api_object = RawAPIObject(idle_ref_in_modpack, - "Idle", api_objects) - idle_raw_api_object.set_location(unit_forward_ref) - idle_raw_api_object.add_raw_parent(ability_parent) - - end_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Default.End") - idle_raw_api_object.add_raw_member("next", end_forward_ref, - ability_parent) - idle_raw_api_object.add_raw_member("ability", - api_objects["engine.ability.type.Idle"], - ability_parent) - - pregen_converter_group.add_raw_api_object(idle_raw_api_object) - pregen_nyan_objects.update({idle_ref_in_modpack: idle_raw_api_object}) - - # End - end_ref_in_modpack = "util.activity.types.Default.End" - end_raw_api_object = RawAPIObject(end_ref_in_modpack, - "End", api_objects) - end_raw_api_object.set_location(unit_forward_ref) - end_raw_api_object.add_raw_parent(end_parent) - - pregen_converter_group.add_raw_api_object(end_raw_api_object) - pregen_nyan_objects.update({end_ref_in_modpack: end_raw_api_object}) - - # ======================================================================= - # Units - # ======================================================================= - unit_ref_in_modpack = "util.activity.types.Unit" - unit_raw_api_object = RawAPIObject(unit_ref_in_modpack, - "Unit", api_objects, - activity_location) - unit_raw_api_object.set_filename("types") - unit_raw_api_object.add_raw_parent(activity_parent) - - start_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.Start") - unit_raw_api_object.add_raw_member("start", start_forward_ref, - activity_parent) - - pregen_converter_group.add_raw_api_object(unit_raw_api_object) - pregen_nyan_objects.update({unit_ref_in_modpack: unit_raw_api_object}) - - unit_forward_ref = ForwardRef(pregen_converter_group, unit_ref_in_modpack) - - # Start - start_ref_in_modpack = "util.activity.types.Unit.Start" - start_raw_api_object = RawAPIObject(start_ref_in_modpack, - "Start", api_objects) - start_raw_api_object.set_location(unit_forward_ref) - start_raw_api_object.add_raw_parent(start_parent) - - idle_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.Idle") - start_raw_api_object.add_raw_member("next", idle_forward_ref, - start_parent) - - pregen_converter_group.add_raw_api_object(start_raw_api_object) - pregen_nyan_objects.update({start_ref_in_modpack: start_raw_api_object}) - - # Idle - idle_ref_in_modpack = "util.activity.types.Unit.Idle" - idle_raw_api_object = RawAPIObject(idle_ref_in_modpack, - "Idle", api_objects) - idle_raw_api_object.set_location(unit_forward_ref) - idle_raw_api_object.add_raw_parent(ability_parent) - - queue_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.CheckQueue") - idle_raw_api_object.add_raw_member("next", queue_forward_ref, - ability_parent) - idle_raw_api_object.add_raw_member("ability", - api_objects["engine.ability.type.Idle"], - ability_parent) - - pregen_converter_group.add_raw_api_object(idle_raw_api_object) - pregen_nyan_objects.update({idle_ref_in_modpack: idle_raw_api_object}) - - # Check if command is in queue - queue_ref_in_modpack = "util.activity.types.Unit.CheckQueue" - queue_raw_api_object = RawAPIObject(queue_ref_in_modpack, - "CheckQueue", api_objects) - queue_raw_api_object.set_location(unit_forward_ref) - queue_raw_api_object.add_raw_parent(xor_parent) - - condition_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.CommandInQueue") - queue_raw_api_object.add_raw_member("next", - [condition_forward_ref], - xor_parent) - command_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.WaitForCommand") - queue_raw_api_object.add_raw_member("default", - command_forward_ref, - xor_parent) - - pregen_converter_group.add_raw_api_object(queue_raw_api_object) - pregen_nyan_objects.update({queue_ref_in_modpack: queue_raw_api_object}) - - # condition for command in queue - condition_ref_in_modpack = "util.activity.types.Unit.CommandInQueue" - 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(cond_queue_parent) - - branch_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.BranchCommand") - condition_raw_api_object.add_raw_member("next", - branch_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}) - - # Wait for Command - command_ref_in_modpack = "util.activity.types.Unit.WaitForCommand" - command_raw_api_object = RawAPIObject(command_ref_in_modpack, - "WaitForCommand", api_objects) - command_raw_api_object.set_location(unit_forward_ref) - command_raw_api_object.add_raw_parent(xor_event_parent) - - event_api_object = api_objects["engine.util.activity.event.type.CommandInQueue"] - branch_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.BranchCommand") - command_raw_api_object.add_raw_member("next", - {event_api_object: branch_forward_ref}, - xor_event_parent) - - pregen_converter_group.add_raw_api_object(command_raw_api_object) - pregen_nyan_objects.update({command_ref_in_modpack: command_raw_api_object}) - - # Branch on command type - branch_ref_in_modpack = "util.activity.types.Unit.BranchCommand" - 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_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_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 based on command - condition_ref_in_modpack = "util.activity.types.Unit.NextCommandSwitch" - 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(cond_command_switch_parent) - - 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"]: ability_check_forward_ref, - api_objects["engine.util.command.type.Move"]: move_forward_ref, - } - condition_raw_api_object.add_raw_member("next", - next_nodes_lookup, - 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}) - - # 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, - "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) - move_to_target_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.MoveToTarget") - range_check_raw_api_object.add_raw_member("default", - move_to_target_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} - ) - - # 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.MoveToTarget"], - task_parent - ) - - pregen_converter_group.add_raw_api_object(move_to_target_raw_api_object) - pregen_nyan_objects.update( - {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, - "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.WaitAbility") - 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}) - - # Move - move_ref_in_modpack = "util.activity.types.Unit.Move" - move_raw_api_object = RawAPIObject(move_ref_in_modpack, - "Move", api_objects) - move_raw_api_object.set_location(unit_forward_ref) - move_raw_api_object.add_raw_parent(ability_parent) - - wait_forward_ref = ForwardRef(pregen_converter_group, - "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", - api_objects["engine.ability.type.Move"], - ability_parent) - - pregen_converter_group.add_raw_api_object(move_raw_api_object) - pregen_nyan_objects.update({move_ref_in_modpack: move_raw_api_object}) - - # 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) - 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"] - wait_raw_api_object.add_raw_member("next", - { - wait_finish: idle_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}) - - # End - end_ref_in_modpack = "util.activity.types.Unit.End" - end_raw_api_object = RawAPIObject(end_ref_in_modpack, - "End", api_objects) - end_raw_api_object.set_location(unit_forward_ref) - end_raw_api_object.add_raw_parent(end_parent) - - pregen_converter_group.add_raw_api_object(end_raw_api_object) - pregen_nyan_objects.update({end_ref_in_modpack: end_raw_api_object}) - - @staticmethod - def generate_attributes( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate Attribute objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - # TODO: Fill translations - # ======================================================================= - attribute_parent = "engine.util.attribute.Attribute" - attributes_location = "data/util/attribute/" - - # ======================================================================= - # HP - # ======================================================================= - health_ref_in_modpack = "util.attribute.types.Health" - health_raw_api_object = RawAPIObject(health_ref_in_modpack, - "Health", api_objects, - attributes_location) - health_raw_api_object.set_filename("types") - health_raw_api_object.add_raw_parent(attribute_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - "util.attribute.types.Health.HealthName") - health_raw_api_object.add_raw_member("name", name_forward_ref, - attribute_parent) - abbrv_forward_ref = ForwardRef(pregen_converter_group, - "util.attribute.types.Health.HealthAbbreviation") - health_raw_api_object.add_raw_member("abbreviation", abbrv_forward_ref, - attribute_parent) - - pregen_converter_group.add_raw_api_object(health_raw_api_object) - pregen_nyan_objects.update({health_ref_in_modpack: health_raw_api_object}) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - health_name_ref_in_modpack = "util.attribute.types.Health.HealthName" - health_name_value = RawAPIObject(health_name_ref_in_modpack, "HealthName", - api_objects, attributes_location) - health_name_value.set_filename("types") - health_name_value.add_raw_parent(name_value_parent) - health_name_value.add_raw_member("translations", [], name_value_parent) - - pregen_converter_group.add_raw_api_object(health_name_value) - pregen_nyan_objects.update({health_name_ref_in_modpack: health_name_value}) - - abbrv_value_parent = "engine.util.language.translated.type.TranslatedString" - health_abbrv_ref_in_modpack = "util.attribute.types.Health.HealthAbbreviation" - health_abbrv_value = RawAPIObject(health_abbrv_ref_in_modpack, "HealthAbbreviation", - api_objects, attributes_location) - health_abbrv_value.set_filename("types") - health_abbrv_value.add_raw_parent(abbrv_value_parent) - health_abbrv_value.add_raw_member("translations", [], abbrv_value_parent) - - pregen_converter_group.add_raw_api_object(health_abbrv_value) - pregen_nyan_objects.update({health_abbrv_ref_in_modpack: health_abbrv_value}) - - # ======================================================================= - # Faith - # ======================================================================= - faith_ref_in_modpack = "util.attribute.types.Faith" - faith_raw_api_object = RawAPIObject(faith_ref_in_modpack, - "Faith", api_objects, - attributes_location) - faith_raw_api_object.set_filename("types") - faith_raw_api_object.add_raw_parent(attribute_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - "util.attribute.types.Faith.FaithName") - faith_raw_api_object.add_raw_member("name", name_forward_ref, - attribute_parent) - abbrv_forward_ref = ForwardRef(pregen_converter_group, - "util.attribute.types.Faith.FaithAbbreviation") - faith_raw_api_object.add_raw_member("abbreviation", abbrv_forward_ref, - attribute_parent) - - pregen_converter_group.add_raw_api_object(faith_raw_api_object) - pregen_nyan_objects.update({faith_ref_in_modpack: faith_raw_api_object}) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - faith_name_ref_in_modpack = "util.attribute.types.Faith.FaithName" - faith_name_value = RawAPIObject(faith_name_ref_in_modpack, "FaithName", - api_objects, attributes_location) - faith_name_value.set_filename("types") - faith_name_value.add_raw_parent(name_value_parent) - faith_name_value.add_raw_member("translations", [], name_value_parent) - - pregen_converter_group.add_raw_api_object(faith_name_value) - pregen_nyan_objects.update({faith_name_ref_in_modpack: faith_name_value}) - - abbrv_value_parent = "engine.util.language.translated.type.TranslatedString" - faith_abbrv_ref_in_modpack = "util.attribute.types.Faith.FaithAbbreviation" - faith_abbrv_value = RawAPIObject(faith_abbrv_ref_in_modpack, "FaithAbbreviation", - api_objects, attributes_location) - faith_abbrv_value.set_filename("types") - faith_abbrv_value.add_raw_parent(abbrv_value_parent) - faith_abbrv_value.add_raw_member("translations", [], abbrv_value_parent) - - pregen_converter_group.add_raw_api_object(faith_abbrv_value) - pregen_nyan_objects.update({faith_abbrv_ref_in_modpack: faith_abbrv_value}) - - @staticmethod - def generate_diplomatic_stances( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate DiplomaticStance objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - stance_parent = "engine.util.diplomatic_stance.DiplomaticStance" - stance_location = "data/util/diplomatic_stance/" - - # ======================================================================= - # Enemy - # ======================================================================= - enemy_ref_in_modpack = "util.diplomatic_stance.types.Enemy" - enemy_raw_api_object = RawAPIObject(enemy_ref_in_modpack, - "Enemy", api_objects, - stance_location) - enemy_raw_api_object.set_filename("types") - enemy_raw_api_object.add_raw_parent(stance_parent) - - pregen_converter_group.add_raw_api_object(enemy_raw_api_object) - pregen_nyan_objects.update({enemy_ref_in_modpack: enemy_raw_api_object}) - - # ======================================================================= - # Neutral - # ======================================================================= - neutral_ref_in_modpack = "util.diplomatic_stance.types.Neutral" - neutral_raw_api_object = RawAPIObject(neutral_ref_in_modpack, - "Neutral", api_objects, - stance_location) - neutral_raw_api_object.set_filename("types") - neutral_raw_api_object.add_raw_parent(stance_parent) - - pregen_converter_group.add_raw_api_object(neutral_raw_api_object) - pregen_nyan_objects.update({neutral_ref_in_modpack: neutral_raw_api_object}) - - # ======================================================================= - # Friendly - # ======================================================================= - friendly_ref_in_modpack = "util.diplomatic_stance.types.Friendly" - friendly_raw_api_object = RawAPIObject(friendly_ref_in_modpack, - "Friendly", api_objects, - stance_location) - friendly_raw_api_object.set_filename("types") - friendly_raw_api_object.add_raw_parent(stance_parent) - - pregen_converter_group.add_raw_api_object(friendly_raw_api_object) - pregen_nyan_objects.update({friendly_ref_in_modpack: friendly_raw_api_object}) - - # ======================================================================= - # Gaia - # ======================================================================= - gaia_ref_in_modpack = "util.diplomatic_stance.types.Gaia" - gaia_raw_api_object = RawAPIObject(gaia_ref_in_modpack, - "Gaia", api_objects, - stance_location) - gaia_raw_api_object.set_filename("types") - gaia_raw_api_object.add_raw_parent(stance_parent) - - pregen_converter_group.add_raw_api_object(gaia_raw_api_object) - pregen_nyan_objects.update({gaia_ref_in_modpack: gaia_raw_api_object}) - - @staticmethod - def generate_team_property( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate the property used in team patches objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - prop_ref_in_modpack = "util.patch.property.types.Team" - prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, - "Team", - api_objects, - "data/util/patch/property/") - prop_raw_api_object.set_filename("types") - prop_raw_api_object.add_raw_parent("engine.util.patch.property.type.Diplomatic") - - pregen_converter_group.add_raw_api_object(prop_raw_api_object) - pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) - - stances = [ - full_data_set.nyan_api_objects["engine.util.diplomatic_stance.type.Self"], - ForwardRef(pregen_converter_group, "util.diplomatic_stance.types.Friendly") - ] - prop_raw_api_object.add_raw_member("stances", - stances, - "engine.util.patch.property.type.Diplomatic") - - @staticmethod - def generate_entity_types( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate GameEntityType objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - class_lookup_dict = internal_name_lookups.get_class_lookups(full_data_set.game_version) - - type_parent = "engine.util.game_entity_type.GameEntityType" - types_location = "data/util/game_entity_type/" - - # ======================================================================= - # Ambient - # ======================================================================= - ambient_ref_in_modpack = "util.game_entity_type.types.Ambient" - ambient_raw_api_object = RawAPIObject(ambient_ref_in_modpack, - "Ambient", api_objects, - types_location) - ambient_raw_api_object.set_filename("types") - ambient_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(ambient_raw_api_object) - pregen_nyan_objects.update({ambient_ref_in_modpack: ambient_raw_api_object}) - - # ======================================================================= - # Building - # ======================================================================= - building_ref_in_modpack = "util.game_entity_type.types.Building" - building_raw_api_object = RawAPIObject(building_ref_in_modpack, - "Building", api_objects, - types_location) - building_raw_api_object.set_filename("types") - building_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(building_raw_api_object) - pregen_nyan_objects.update({building_ref_in_modpack: building_raw_api_object}) - - # ======================================================================= - # Item - # ======================================================================= - item_ref_in_modpack = "util.game_entity_type.types.Item" - item_raw_api_object = RawAPIObject(item_ref_in_modpack, - "Item", api_objects, - types_location) - item_raw_api_object.set_filename("types") - item_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(item_raw_api_object) - pregen_nyan_objects.update({item_ref_in_modpack: item_raw_api_object}) - - # ======================================================================= - # Projectile - # ======================================================================= - projectile_ref_in_modpack = "util.game_entity_type.types.Projectile" - projectile_raw_api_object = RawAPIObject(projectile_ref_in_modpack, - "Projectile", api_objects, - types_location) - projectile_raw_api_object.set_filename("types") - projectile_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(projectile_raw_api_object) - pregen_nyan_objects.update({projectile_ref_in_modpack: projectile_raw_api_object}) - - # ======================================================================= - # Unit - # ======================================================================= - unit_ref_in_modpack = "util.game_entity_type.types.Unit" - unit_raw_api_object = RawAPIObject(unit_ref_in_modpack, - "Unit", api_objects, - types_location) - unit_raw_api_object.set_filename("types") - unit_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(unit_raw_api_object) - pregen_nyan_objects.update({unit_ref_in_modpack: unit_raw_api_object}) - - # ======================================================================= - # DropSite - # ======================================================================= - drop_site_ref_in_modpack = "util.game_entity_type.types.DropSite" - drop_site_raw_api_object = RawAPIObject(drop_site_ref_in_modpack, - "DropSite", api_objects, - types_location) - drop_site_raw_api_object.set_filename("types") - drop_site_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(drop_site_raw_api_object) - pregen_nyan_objects.update({drop_site_ref_in_modpack: drop_site_raw_api_object}) - - # ======================================================================= - # Others (generated from class ID) - # ======================================================================= - converter_groups = [] - converter_groups.extend(full_data_set.unit_lines.values()) - converter_groups.extend(full_data_set.building_lines.values()) - converter_groups.extend(full_data_set.ambient_groups.values()) - converter_groups.extend(full_data_set.variant_groups.values()) - - for unit_line in converter_groups: - unit_class = unit_line.get_class_id() - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - - new_game_entity_type = RawAPIObject(class_obj_name, class_name, - full_data_set.nyan_api_objects, - types_location) - new_game_entity_type.set_filename("types") - new_game_entity_type.add_raw_parent("engine.util.game_entity_type.GameEntityType") - new_game_entity_type.create_nyan_object() - - pregen_converter_group.add_raw_api_object(new_game_entity_type) - pregen_nyan_objects.update({class_obj_name: new_game_entity_type}) - - @staticmethod - def generate_effect_types( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate types for effects and resistances. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(full_data_set.game_version) - armor_lookup_dict = internal_name_lookups.get_armor_class_lookups( - full_data_set.game_version) - - # ======================================================================= - # Armor types - # ======================================================================= - type_parent = "engine.util.attribute_change_type.AttributeChangeType" - types_location = "data/util/attribute_change_type/" - - for type_name in armor_lookup_dict.values(): - type_ref_in_modpack = f"util.attribute_change_type.types.{type_name}" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - type_name, api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # Heal - # ======================================================================= - type_ref_in_modpack = "util.attribute_change_type.types.Heal" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - "Heal", api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # Repair (one for each repairable entity) - # ======================================================================= - repairable_lines = [] - repairable_lines.extend(full_data_set.building_lines.values()) - for unit_line in full_data_set.unit_lines.values(): - if unit_line.is_repairable(): - repairable_lines.append(unit_line) - - for repairable_line in repairable_lines: - game_entity_name = name_lookup_dict[repairable_line.get_head_unit_id()][0] - - type_ref_in_modpack = f"util.attribute_change_type.types.{game_entity_name}Repair" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - f"{game_entity_name}Repair", - api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # Construct (two for each constructable entity) - # ======================================================================= - constructable_lines = [] - constructable_lines.extend(full_data_set.building_lines.values()) - - for constructable_line in constructable_lines: - game_entity_name = name_lookup_dict[constructable_line.get_head_unit_id()][0] - - type_ref_in_modpack = f"util.attribute_change_type.types.{game_entity_name}Construct" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - f"{game_entity_name}Construct", - api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - type_parent = "engine.util.progress_type.type.Construct" - types_location = "data/util/construct_type/" - - for constructable_line in constructable_lines: - game_entity_name = name_lookup_dict[constructable_line.get_head_unit_id()][0] - - type_ref_in_modpack = f"util.construct_type.types.{game_entity_name}Construct" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - f"{game_entity_name}Construct", - api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # ConvertType: UnitConvert - # ======================================================================= - type_parent = "engine.util.convert_type.ConvertType" - types_location = "data/util/convert_type/" - - type_ref_in_modpack = "util.convert_type.types.UnitConvert" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - "UnitConvert", api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # ConvertType: BuildingConvert - # ======================================================================= - type_parent = "engine.util.convert_type.ConvertType" - types_location = "data/util/convert_type/" - - type_ref_in_modpack = "util.convert_type.types.BuildingConvert" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - "BuildingConvert", api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - @staticmethod - def generate_exchange_objects( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate objects for market trading (ExchangeResources). - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - # ======================================================================= - # Exchange mode Buy - # ======================================================================= - exchange_mode_parent = "engine.util.exchange_mode.type.Buy" - exchange_mode_location = "data/util/resource/" - - exchange_mode_ref_in_modpack = "util.resource.market_trading.MarketBuyExchangeMode" - exchange_mode_raw_api_object = RawAPIObject(exchange_mode_ref_in_modpack, - "MarketBuyExchangePool", - api_objects, - exchange_mode_location) - exchange_mode_raw_api_object.set_filename("market_trading") - exchange_mode_raw_api_object.add_raw_parent(exchange_mode_parent) - - # Fee (30% on top) - exchange_mode_raw_api_object.add_raw_member("fee_multiplier", - 1.3, - "engine.util.exchange_mode.ExchangeMode") - - pregen_converter_group.add_raw_api_object(exchange_mode_raw_api_object) - pregen_nyan_objects.update({exchange_mode_ref_in_modpack: exchange_mode_raw_api_object}) - - # ======================================================================= - # Exchange mode Sell - # ======================================================================= - exchange_mode_parent = "engine.util.exchange_mode.type.Sell" - exchange_mode_location = "data/util/resource/" - - exchange_mode_ref_in_modpack = "util.resource.market_trading.MarketSellExchangeMode" - exchange_mode_raw_api_object = RawAPIObject(exchange_mode_ref_in_modpack, - "MarketSellExchangeMode", - api_objects, - exchange_mode_location) - exchange_mode_raw_api_object.set_filename("market_trading") - exchange_mode_raw_api_object.add_raw_parent(exchange_mode_parent) - - # Fee (30% reduced) - exchange_mode_raw_api_object.add_raw_member("fee_multiplier", - 0.7, - "engine.util.exchange_mode.ExchangeMode") - - pregen_converter_group.add_raw_api_object(exchange_mode_raw_api_object) - pregen_nyan_objects.update({exchange_mode_ref_in_modpack: exchange_mode_raw_api_object}) - - # ======================================================================= - # Market Food price pool - # ======================================================================= - exchange_pool_parent = "engine.util.price_pool.PricePool" - exchange_pool_location = "data/util/resource/" - - exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketFoodPricePool" - exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, - "MarketFoodPricePool", - api_objects, - exchange_pool_location) - exchange_pool_raw_api_object.set_filename("market_trading") - exchange_pool_raw_api_object.add_raw_parent(exchange_pool_parent) - - pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) - pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) - - # ======================================================================= - # Market Wood price pool - # ======================================================================= - exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketWoodPricePool" - exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, - "MarketWoodPricePool", - api_objects, - exchange_pool_location) - exchange_pool_raw_api_object.set_filename("market_trading") - exchange_pool_raw_api_object.add_raw_parent(exchange_pool_parent) - - pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) - pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) - - # ======================================================================= - # Market Stone price pool - # ======================================================================= - exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketStonePricePool" - exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, - "MarketStonePricePool", - api_objects, - exchange_pool_location) - exchange_pool_raw_api_object.set_filename("market_trading") - exchange_pool_raw_api_object.add_raw_parent(exchange_pool_parent) - - pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) - pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) - - # ======================================================================= - # Exchange rate Food - # ======================================================================= - exchange_rate_parent = "engine.util.exchange_rate.ExchangeRate" - exchange_rate_location = "data/util/resource/" - - exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketFoodExchangeRate" - exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, - "MarketFoodExchangeRate", - api_objects, - exchange_rate_location) - exchange_rate_raw_api_object.set_filename("market_trading") - exchange_rate_raw_api_object.add_raw_parent(exchange_rate_parent) - - # Base price - exchange_rate_raw_api_object.add_raw_member("base_price", - 1.0, - exchange_rate_parent) - - # Price adjust methods - pa_buy_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketBuyPriceMode") - pa_sell_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketSellPriceMode") - price_adjust = { - api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, - api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref - } - exchange_rate_raw_api_object.add_raw_member("price_adjust", - price_adjust, - exchange_rate_parent) - - # Price pool - pool_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketFoodPricePool") - exchange_rate_raw_api_object.add_raw_member("price_pool", - pool_forward_ref, - exchange_rate_parent) - - pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) - pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) - - # ======================================================================= - # Exchange rate Wood - # ======================================================================= - exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketWoodExchangeRate" - exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, - "MarketWoodExchangeRate", - api_objects, - exchange_rate_location) - exchange_rate_raw_api_object.set_filename("market_trading") - exchange_rate_raw_api_object.add_raw_parent(exchange_rate_parent) - - # Base price - exchange_rate_raw_api_object.add_raw_member("base_price", - 1.0, - exchange_rate_parent) - - # Price adjust methods - pa_buy_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketBuyPriceMode") - pa_sell_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketSellPriceMode") - price_adjust = { - api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, - api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref - } - exchange_rate_raw_api_object.add_raw_member("price_adjust", - price_adjust, - exchange_rate_parent) - - # Price pool - pool_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketWoodPricePool") - exchange_rate_raw_api_object.add_raw_member("price_pool", - pool_forward_ref, - exchange_rate_parent) - - pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) - pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) - - # ======================================================================= - # Exchange rate Stone - # ======================================================================= - exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketStoneExchangeRate" - exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, - "MarketStoneExchangeRate", - api_objects, - exchange_rate_location) - exchange_rate_raw_api_object.set_filename("market_trading") - exchange_rate_raw_api_object.add_raw_parent(exchange_rate_parent) - - # Base price - exchange_rate_raw_api_object.add_raw_member("base_price", - 1.3, - exchange_rate_parent) - - # Price adjust methods - pa_buy_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketBuyPriceMode") - pa_sell_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketSellPriceMode") - price_adjust = { - api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, - api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref - } - exchange_rate_raw_api_object.add_raw_member("price_adjust", - price_adjust, - exchange_rate_parent) - - # Price pool - pool_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketStonePricePool") - exchange_rate_raw_api_object.add_raw_member("price_pool", - pool_forward_ref, - exchange_rate_parent) - - pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) - pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) - - # ======================================================================= - # Buy Price mode - # ======================================================================= - price_mode_parent = "engine.util.price_mode.type.Dynamic" - price_mode_location = "data/util/resource/" - - price_mode_ref_in_modpack = "util.resource.market_trading.MarketBuyPriceMode" - price_mode_raw_api_object = RawAPIObject(price_mode_ref_in_modpack, - "MarketBuyPriceMode", - api_objects, - price_mode_location) - price_mode_raw_api_object.set_filename("market_trading") - price_mode_raw_api_object.add_raw_parent(price_mode_parent) - - # Min price - price_mode_raw_api_object.add_raw_member("change_value", - 0.03, - price_mode_parent) - - # Min price - price_mode_raw_api_object.add_raw_member("min_price", - 0.3, - price_mode_parent) - - # Max price - price_mode_raw_api_object.add_raw_member("max_price", - 99.9, - price_mode_parent) - - pregen_converter_group.add_raw_api_object(price_mode_raw_api_object) - pregen_nyan_objects.update({price_mode_ref_in_modpack: price_mode_raw_api_object}) - - # ======================================================================= - # Sell Price mode - # ======================================================================= - price_mode_parent = "engine.util.price_mode.type.Dynamic" - price_mode_location = "data/util/resource/" - - price_mode_ref_in_modpack = "util.resource.market_trading.MarketSellPriceMode" - price_mode_raw_api_object = RawAPIObject(price_mode_ref_in_modpack, - "MarketSellPriceMode", - api_objects, - price_mode_location) - price_mode_raw_api_object.set_filename("market_trading") - price_mode_raw_api_object.add_raw_parent(price_mode_parent) - - # Min price - price_mode_raw_api_object.add_raw_member("change_value", - -0.03, - price_mode_parent) - - # Min price - price_mode_raw_api_object.add_raw_member("min_price", - 0.3, - price_mode_parent) - - # Max price - price_mode_raw_api_object.add_raw_member("max_price", - 99.9, - price_mode_parent) - - pregen_converter_group.add_raw_api_object(price_mode_raw_api_object) - pregen_nyan_objects.update({price_mode_ref_in_modpack: price_mode_raw_api_object}) - - @staticmethod - def generate_formation_types( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate Formation and Subformation objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - # ======================================================================= - # Line formation - # ======================================================================= - formation_parent = "engine.util.formation.Formation" - formation_location = "data/util/formation/" - - formation_ref_in_modpack = "util.formation.types.Line" - formation_raw_api_object = RawAPIObject(formation_ref_in_modpack, - "Line", - api_objects, - formation_location) - formation_raw_api_object.set_filename("types") - formation_raw_api_object.add_raw_parent(formation_parent) - - subformations = [ - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Cavalry"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Infantry"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Ranged"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Siege"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Support"), - ] - formation_raw_api_object.add_raw_member("subformations", - subformations, - formation_parent) - - pregen_converter_group.add_raw_api_object(formation_raw_api_object) - pregen_nyan_objects.update({formation_ref_in_modpack: formation_raw_api_object}) - # ======================================================================= - # Staggered formation - # ======================================================================= - formation_ref_in_modpack = "util.formation.types.Staggered" - formation_raw_api_object = RawAPIObject(formation_ref_in_modpack, - "Staggered", - api_objects, - formation_location) - formation_raw_api_object.set_filename("types") - formation_raw_api_object.add_raw_parent(formation_parent) - - subformations = [ - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Cavalry"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Infantry"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Ranged"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Siege"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Support"), - ] - formation_raw_api_object.add_raw_member("subformations", - subformations, - formation_parent) - - pregen_converter_group.add_raw_api_object(formation_raw_api_object) - pregen_nyan_objects.update({formation_ref_in_modpack: formation_raw_api_object}) - # ======================================================================= - # Box formation - # ======================================================================= - formation_ref_in_modpack = "util.formation.types.Box" - formation_raw_api_object = RawAPIObject(formation_ref_in_modpack, - "Box", - api_objects, - formation_location) - formation_raw_api_object.set_filename("types") - formation_raw_api_object.add_raw_parent(formation_parent) - - subformations = [ - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Cavalry"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Infantry"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Ranged"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Siege"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Support"), - ] - formation_raw_api_object.add_raw_member("subformations", - subformations, - formation_parent) - - pregen_converter_group.add_raw_api_object(formation_raw_api_object) - pregen_nyan_objects.update({formation_ref_in_modpack: formation_raw_api_object}) - # ======================================================================= - # Flank formation - # ======================================================================= - formation_ref_in_modpack = "util.formation.types.Flank" - formation_raw_api_object = RawAPIObject(formation_ref_in_modpack, - "Flank", - api_objects, - formation_location) - formation_raw_api_object.set_filename("types") - formation_raw_api_object.add_raw_parent(formation_parent) - - subformations = [ - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Cavalry"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Infantry"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Ranged"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Siege"), - ForwardRef(pregen_converter_group, "util.formation.subformation.types.Support"), - ] - formation_raw_api_object.add_raw_member("subformations", - subformations, - formation_parent) - - pregen_converter_group.add_raw_api_object(formation_raw_api_object) - pregen_nyan_objects.update({formation_ref_in_modpack: formation_raw_api_object}) - - # ======================================================================= - # Cavalry subformation - # ======================================================================= - subformation_parent = "engine.util.formation.Subformation" - subformation_location = "data/util/formation/" - - subformation_ref_in_modpack = "util.formation.subformation.types.Cavalry" - subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, - "Cavalry", - api_objects, - subformation_location) - subformation_raw_api_object.set_filename("subformations") - subformation_raw_api_object.add_raw_parent(subformation_parent) - - subformation_raw_api_object.add_raw_member("ordering_priority", - 5, - subformation_parent) - - pregen_converter_group.add_raw_api_object(subformation_raw_api_object) - pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) - - # ======================================================================= - # Infantry subformation - # ======================================================================= - subformation_ref_in_modpack = "util.formation.subformation.types.Infantry" - subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, - "Infantry", - api_objects, - subformation_location) - subformation_raw_api_object.set_filename("subformations") - subformation_raw_api_object.add_raw_parent(subformation_parent) - - subformation_raw_api_object.add_raw_member("ordering_priority", - 4, - subformation_parent) - - pregen_converter_group.add_raw_api_object(subformation_raw_api_object) - pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) - - # ======================================================================= - # Ranged subformation - # ======================================================================= - subformation_ref_in_modpack = "util.formation.subformation.types.Ranged" - subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, - "Ranged", - api_objects, - subformation_location) - subformation_raw_api_object.set_filename("subformations") - subformation_raw_api_object.add_raw_parent(subformation_parent) - - subformation_raw_api_object.add_raw_member("ordering_priority", - 3, - subformation_parent) - - pregen_converter_group.add_raw_api_object(subformation_raw_api_object) - pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) - - # ======================================================================= - # Siege subformation - # ======================================================================= - subformation_ref_in_modpack = "util.formation.subformation.types.Siege" - subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, - "Siege", - api_objects, - subformation_location) - subformation_raw_api_object.set_filename("subformations") - subformation_raw_api_object.add_raw_parent(subformation_parent) - - subformation_raw_api_object.add_raw_member("ordering_priority", - 2, - subformation_parent) - - pregen_converter_group.add_raw_api_object(subformation_raw_api_object) - pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) - - # ======================================================================= - # Support subformation - # ======================================================================= - subformation_ref_in_modpack = "util.formation.subformation.types.Support" - subformation_raw_api_object = RawAPIObject(subformation_ref_in_modpack, - "Support", - api_objects, - subformation_location) - subformation_raw_api_object.set_filename("subformations") - subformation_raw_api_object.add_raw_parent(subformation_parent) - - subformation_raw_api_object.add_raw_member("ordering_priority", - 1, - subformation_parent) - - pregen_converter_group.add_raw_api_object(subformation_raw_api_object) - pregen_nyan_objects.update({subformation_ref_in_modpack: subformation_raw_api_object}) - - @staticmethod - def generate_language_objects( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate language objects from the string resources - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - language_parent = "engine.util.language.Language" - language_location = "data/util/language/" - - languages = full_data_set.strings.get_tables().keys() - - for language in languages: - language_ref_in_modpack = f"util.language.{language}" - language_raw_api_object = RawAPIObject(language_ref_in_modpack, - language, - api_objects, - language_location) - language_raw_api_object.set_filename("language") - language_raw_api_object.add_raw_parent(language_parent) - - language_raw_api_object.add_raw_member("ietf_string", - language, - language_parent) - - pregen_converter_group.add_raw_api_object(language_raw_api_object) - pregen_nyan_objects.update({language_ref_in_modpack: language_raw_api_object}) - - @staticmethod - def generate_misc_effect_objects( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate fallback types and other standard objects for effects and resistances. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - # ======================================================================= - # Min change value (lower cealing for attack effects) - # ======================================================================= - min_change_parent = "engine.util.attribute.AttributeAmount" - min_change_location = "data/effect/discrete/flat_attribute_change/" - - change_ref_in_modpack = "effect.discrete.flat_attribute_change.min_damage.AoE2MinChangeAmount" - change_raw_api_object = RawAPIObject(change_ref_in_modpack, - "AoE2MinChangeAmount", - api_objects, - min_change_location) - change_raw_api_object.set_filename("min_damage") - change_raw_api_object.add_raw_parent(min_change_parent) - - attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") - change_raw_api_object.add_raw_member("type", - attribute, - min_change_parent) - change_raw_api_object.add_raw_member("amount", - 0, - min_change_parent) - - pregen_converter_group.add_raw_api_object(change_raw_api_object) - pregen_nyan_objects.update({change_ref_in_modpack: change_raw_api_object}) - - # ======================================================================= - # Min change value (lower cealing for heal effects) - # ======================================================================= - min_change_parent = "engine.util.attribute.AttributeRate" - min_change_location = "data/effect/discrete/flat_attribute_change/" - - change_ref_in_modpack = "effect.discrete.flat_attribute_change.min_heal.AoE2MinChangeAmount" - change_raw_api_object = RawAPIObject(change_ref_in_modpack, - "AoE2MinChangeAmount", - api_objects, - min_change_location) - change_raw_api_object.set_filename("min_heal") - change_raw_api_object.add_raw_parent(min_change_parent) - - attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") - change_raw_api_object.add_raw_member("type", - attribute, - min_change_parent) - change_raw_api_object.add_raw_member("rate", - 0, - min_change_parent) - - pregen_converter_group.add_raw_api_object(change_raw_api_object) - pregen_nyan_objects.update({change_ref_in_modpack: change_raw_api_object}) - - # ======================================================================= - # Fallback effect for attacking (= minimum damage) - # ======================================================================= - effect_parent = "engine.effect.discrete.flat_attribute_change.FlatAttributeChange" - fallback_parent = "engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" - fallback_location = "data/effect/discrete/flat_attribute_change/" - - fallback_ref_in_modpack = "effect.discrete.flat_attribute_change.fallback.AoE2AttackFallback" - fallback_raw_api_object = RawAPIObject(fallback_ref_in_modpack, - "AoE2AttackFallback", - api_objects, - fallback_location) - fallback_raw_api_object.set_filename("fallback") - fallback_raw_api_object.add_raw_parent(fallback_parent) - - # Type - type_ref = "engine.util.attribute_change_type.type.Fallback" - change_type = api_objects[type_ref] - fallback_raw_api_object.add_raw_member("type", - change_type, - effect_parent) - - # Min value (optional) - # ================================================================================= - amount_name = f"{fallback_ref_in_modpack}.LowerCealing" - amount_raw_api_object = RawAPIObject(amount_name, "LowerCealing", api_objects) - amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") - amount_location = ForwardRef(pregen_converter_group, fallback_ref_in_modpack) - amount_raw_api_object.set_location(amount_location) - - attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") - amount_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeAmount") - amount_raw_api_object.add_raw_member("amount", - 1, - "engine.util.attribute.AttributeAmount") - - pregen_converter_group.add_raw_api_object(amount_raw_api_object) - pregen_nyan_objects.update({amount_name: amount_raw_api_object}) - # ================================================================================= - amount_forward_ref = ForwardRef(pregen_converter_group, amount_name) - fallback_raw_api_object.add_raw_member("min_change_value", - amount_forward_ref, - effect_parent) - - # Max value (optional; not needed - - # Change value - # ================================================================================= - amount_name = f"{fallback_ref_in_modpack}.ChangeAmount" - amount_raw_api_object = RawAPIObject(amount_name, "ChangeAmount", api_objects) - amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") - amount_location = ForwardRef(pregen_converter_group, fallback_ref_in_modpack) - amount_raw_api_object.set_location(amount_location) - - attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") - amount_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeAmount") - amount_raw_api_object.add_raw_member("amount", - 1, - "engine.util.attribute.AttributeAmount") - - pregen_converter_group.add_raw_api_object(amount_raw_api_object) - pregen_nyan_objects.update({amount_name: amount_raw_api_object}) - - # ================================================================================= - amount_forward_ref = ForwardRef(pregen_converter_group, amount_name) - fallback_raw_api_object.add_raw_member("change_value", - amount_forward_ref, - effect_parent) - - # Ignore protection - fallback_raw_api_object.add_raw_member("ignore_protection", - [], - effect_parent) - - pregen_converter_group.add_raw_api_object(fallback_raw_api_object) - pregen_nyan_objects.update({fallback_ref_in_modpack: fallback_raw_api_object}) - - # ======================================================================= - # Fallback resistance - # ======================================================================= - effect_parent = "engine.resistance.discrete.flat_attribute_change.FlatAttributeChange" - fallback_parent = "engine.resistance.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" - fallback_location = "data/resistance/discrete/flat_attribute_change/" - - fallback_ref_in_modpack = "resistance.discrete.flat_attribute_change.fallback.AoE2AttackFallback" - fallback_raw_api_object = RawAPIObject(fallback_ref_in_modpack, - "AoE2AttackFallback", - api_objects, - fallback_location) - fallback_raw_api_object.set_filename("fallback") - fallback_raw_api_object.add_raw_parent(fallback_parent) - - # Type - type_ref = "engine.util.attribute_change_type.type.Fallback" - change_type = api_objects[type_ref] - fallback_raw_api_object.add_raw_member("type", - change_type, - effect_parent) - - # Block value - # ================================================================================= - amount_name = f"{fallback_ref_in_modpack}.BlockAmount" - amount_raw_api_object = RawAPIObject(amount_name, "BlockAmount", api_objects) - amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") - amount_location = ForwardRef(pregen_converter_group, fallback_ref_in_modpack) - amount_raw_api_object.set_location(amount_location) - - attribute = ForwardRef(pregen_converter_group, "util.attribute.types.Health") - amount_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeAmount") - amount_raw_api_object.add_raw_member("amount", - 0, - "engine.util.attribute.AttributeAmount") - - pregen_converter_group.add_raw_api_object(amount_raw_api_object) - pregen_nyan_objects.update({amount_name: amount_raw_api_object}) - - # ================================================================================= - amount_forward_ref = ForwardRef(pregen_converter_group, amount_name) - fallback_raw_api_object.add_raw_member("block_value", - amount_forward_ref, - effect_parent) - - pregen_converter_group.add_raw_api_object(fallback_raw_api_object) - pregen_nyan_objects.update({fallback_ref_in_modpack: fallback_raw_api_object}) - - # ======================================================================= - # Property Construct - # ======================================================================= - prop_ref_in_modpack = "resistance.property.types.BuildingConstruct" - prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, - "BuildingConstruct", - api_objects, - "data/resistance/property/") - prop_raw_api_object.set_filename("types") - prop_raw_api_object.add_raw_parent("engine.resistance.property.type.Stacked") - - pregen_converter_group.add_raw_api_object(prop_raw_api_object) - pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) - - prop_raw_api_object.add_raw_member("stack_limit", - MemberSpecialValue.NYAN_INF, - "engine.resistance.property.type.Stacked") - - prop_raw_api_object.add_raw_member("distribution_type", - api_objects["engine.util.distribution_type.type.Mean"], - "engine.resistance.property.type.Stacked") - - # Calculation type Construct - # ======================================================================= - calc_parent = "engine.util.calculation_type.type.Hyperbolic" - - calc_ref_in_modpack = "util.calculation_type.construct_calculation.ConstructCalcType" - calc_raw_api_object = RawAPIObject(calc_ref_in_modpack, - "BuildingConstruct", - api_objects) - calc_location = ForwardRef(pregen_converter_group, prop_ref_in_modpack) - calc_raw_api_object.set_location(calc_location) - calc_raw_api_object.add_raw_parent(calc_parent) - - pregen_converter_group.add_raw_api_object(calc_raw_api_object) - pregen_nyan_objects.update({calc_ref_in_modpack: calc_raw_api_object}) - - # Formula: (scale_factor / (count_effectors - shift_x)) + shift_y - # AoE2: (3 / (vil_count + 2)) - - # Shift x - calc_raw_api_object.add_raw_member("shift_x", - -2, - calc_parent) - - # Shift y - calc_raw_api_object.add_raw_member("shift_y", - 0, - calc_parent) - - # Scale - calc_raw_api_object.add_raw_member("scale_factor", - 3, - calc_parent) - - calc_forward_ref = ForwardRef(pregen_converter_group, calc_ref_in_modpack) - prop_raw_api_object.add_raw_member("calculation_type", - calc_forward_ref, - "engine.resistance.property.type.Stacked") - - # ======================================================================= - # Property Repair - # ======================================================================= - prop_ref_in_modpack = "resistance.property.types.BuildingRepair" - prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, - "BuildingRepair", - api_objects, - "data/resistance/property/") - prop_raw_api_object.set_filename("types") - prop_raw_api_object.add_raw_parent("engine.resistance.property.type.Stacked") - - pregen_converter_group.add_raw_api_object(prop_raw_api_object) - pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) - - prop_raw_api_object.add_raw_member("stack_limit", - MemberSpecialValue.NYAN_INF, - "engine.resistance.property.type.Stacked") - - prop_raw_api_object.add_raw_member("distribution_type", - api_objects["engine.util.distribution_type.type.Mean"], - "engine.resistance.property.type.Stacked") - - # ======================================================================= - # Calculation type Repair - # ======================================================================= - calc_parent = "engine.util.calculation_type.type.Linear" - - calc_ref_in_modpack = "util.calculation_type.construct_calculation.BuildingRepair" - calc_raw_api_object = RawAPIObject(calc_ref_in_modpack, - "BuildingRepair", - api_objects) - calc_location = ForwardRef(pregen_converter_group, prop_ref_in_modpack) - calc_raw_api_object.set_location(calc_location) - calc_raw_api_object.add_raw_parent(calc_parent) - - pregen_converter_group.add_raw_api_object(calc_raw_api_object) - pregen_nyan_objects.update({calc_ref_in_modpack: calc_raw_api_object}) - - # Formula: (scale_factor * (count_effectors - shift_x)) + shift_y - # AoE2: (0.333334 * (vil_count + 2)) - - # Shift x - calc_raw_api_object.add_raw_member("shift_x", - -2, - calc_parent) - - # Shift y - calc_raw_api_object.add_raw_member("shift_y", - 0, - calc_parent) - - # Scale - calc_raw_api_object.add_raw_member("scale_factor", - 1 / 3, - calc_parent) - - calc_forward_ref = ForwardRef(pregen_converter_group, calc_ref_in_modpack) - prop_raw_api_object.add_raw_member("calculation_type", - calc_forward_ref, - "engine.resistance.property.type.Stacked") - - @staticmethod - def generate_modifiers( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate standard modifiers. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - modifier_parent = "engine.modifier.Modifier" - mprop_parent = "engine.modifier.property.type.Multiplier" - type_parent = "engine.modifier.effect.flat_attribute_change.type.Flyover" - types_location = "data/util/modifier/flyover_cliff/" - - # ======================================================================= - # Flyover effect multiplier - # ======================================================================= - modifier_ref_in_modpack = "util.modifier.flyover_cliff.AttackFlyover" - modifier_raw_api_object = RawAPIObject(modifier_ref_in_modpack, - "AttackFlyover", api_objects, - types_location) - modifier_raw_api_object.set_filename("flyover_cliff") - modifier_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(modifier_raw_api_object) - pregen_nyan_objects.update({modifier_ref_in_modpack: modifier_raw_api_object}) - - # Relative angle to cliff must not be smaller than 90° - modifier_raw_api_object.add_raw_member("relative_angle", - 90, - type_parent) - - # Affects all cliffs - types = [ForwardRef(pregen_converter_group, "util.game_entity_type.types.Cliff")] - modifier_raw_api_object.add_raw_member("flyover_types", - types, - type_parent) - modifier_raw_api_object.add_raw_member("blacklisted_entities", - [], - type_parent) - - # Multiplier property: Increases effect value by 25% - # -------------------------------------------------- - prop_ref_in_modpack = "util.modifier.flyover_cliff.AttackFlyover.Multiplier" - prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, - "Multiplier", api_objects, - types_location) - prop_location = ForwardRef(pregen_converter_group, modifier_ref_in_modpack) - prop_raw_api_object.set_location(prop_location) - prop_raw_api_object.add_raw_parent(mprop_parent) - - pregen_converter_group.add_raw_api_object(prop_raw_api_object) - pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) - - prop_raw_api_object.add_raw_member("multiplier", - 1.25, - mprop_parent) - # -------------------------------------------------- - # Assign property to modifier - prop_forward_ref = ForwardRef(pregen_converter_group, prop_ref_in_modpack) - properties = {api_objects[mprop_parent]: prop_forward_ref} - modifier_raw_api_object.add_raw_member("properties", - properties, - modifier_parent) - - # ======================================================================= - # Elevation difference effect multiplier (higher unit) - # ======================================================================= - type_parent = "engine.modifier.effect.flat_attribute_change.type.ElevationDifferenceHigh" - types_location = "data/util/modifier/elevation_difference/" - - modifier_ref_in_modpack = "util.modifier.elevation_difference.AttackHigh" - modifier_raw_api_object = RawAPIObject(modifier_ref_in_modpack, - "AttackHigh", api_objects, - types_location) - modifier_raw_api_object.set_filename("elevation_difference") - modifier_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(modifier_raw_api_object) - pregen_nyan_objects.update({modifier_ref_in_modpack: modifier_raw_api_object}) - - # Multiplier property: Increases effect value to 125% - # -------------------------------------------------- - prop_ref_in_modpack = "util.modifier.elevation_difference.AttackHigh.Multiplier" - prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, - "Multiplier", api_objects, - types_location) - prop_location = ForwardRef(pregen_converter_group, modifier_ref_in_modpack) - prop_raw_api_object.set_location(prop_location) - prop_raw_api_object.add_raw_parent(mprop_parent) - - pregen_converter_group.add_raw_api_object(prop_raw_api_object) - pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) - - prop_raw_api_object.add_raw_member("multiplier", - 1.25, - mprop_parent) - # -------------------------------------------------- - # Assign property to modifier - prop_forward_ref = ForwardRef(pregen_converter_group, prop_ref_in_modpack) - properties = {api_objects[mprop_parent]: prop_forward_ref} - modifier_raw_api_object.add_raw_member("properties", - properties, - modifier_parent) - - # ======================================================================= - # Elevation difference effect multiplier (lower unit) - # ======================================================================= - type_parent = "engine.modifier.effect.flat_attribute_change.type.ElevationDifferenceLow" - types_location = "data/util/modifier/elevation_difference/" - - modifier_ref_in_modpack = "util.modifier.elevation_difference.AttackLow" - modifier_raw_api_object = RawAPIObject(modifier_ref_in_modpack, - "AttackLow", api_objects, - types_location) - modifier_raw_api_object.set_filename("elevation_difference") - modifier_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(modifier_raw_api_object) - pregen_nyan_objects.update({modifier_ref_in_modpack: modifier_raw_api_object}) - - # Multiplier property: Decreases effect value to 75% - # -------------------------------------------------- - prop_ref_in_modpack = "util.modifier.elevation_difference.AttackLow.Multiplier" - prop_raw_api_object = RawAPIObject(prop_ref_in_modpack, - "Multiplier", api_objects, - types_location) - prop_location = ForwardRef(pregen_converter_group, modifier_ref_in_modpack) - prop_raw_api_object.set_location(prop_location) - prop_raw_api_object.add_raw_parent(mprop_parent) - - pregen_converter_group.add_raw_api_object(prop_raw_api_object) - pregen_nyan_objects.update({prop_ref_in_modpack: prop_raw_api_object}) - - prop_raw_api_object.add_raw_member("multiplier", - 1.25, - mprop_parent) - # -------------------------------------------------- - # Assign property to modifier - prop_forward_ref = ForwardRef(pregen_converter_group, prop_ref_in_modpack) - properties = {api_objects[mprop_parent]: prop_forward_ref} - modifier_raw_api_object.add_raw_member("properties", - properties, - modifier_parent) - - @staticmethod - def generate_terrain_types( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate TerrainType objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( - full_data_set.game_version) - - type_parent = "engine.util.terrain_type.TerrainType" - types_location = "data/util/terrain_type/" - - terrain_type_lookups = terrain_type_lookup_dict.values() - - for terrain_type in terrain_type_lookups: - type_name = terrain_type[2] - type_ref_in_modpack = f"util.terrain_type.types.{type_name}" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - type_name, api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - @staticmethod - def generate_path_types( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate PathType objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - path_type_parent = "engine.util.path_type.PathType" - path_types_location = "data/util/path_type/" - - # ======================================================================= - # Land - # ======================================================================= - path_type_ref_in_modpack = "util.path.types.Land" - path_type_raw_api_object = RawAPIObject(path_type_ref_in_modpack, - "Land", - api_objects, - path_types_location) - path_type_raw_api_object.set_filename("types") - path_type_raw_api_object.add_raw_parent(path_type_parent) - - pregen_converter_group.add_raw_api_object(path_type_raw_api_object) - pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) - - # ======================================================================= - # Water - # ======================================================================= - path_type_ref_in_modpack = "util.path.types.Water" - path_type_raw_api_object = RawAPIObject(path_type_ref_in_modpack, - "Water", - api_objects, - path_types_location) - path_type_raw_api_object.set_filename("types") - path_type_raw_api_object.add_raw_parent(path_type_parent) - - pregen_converter_group.add_raw_api_object(path_type_raw_api_object) - pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) - - # ======================================================================= - # Air - # ======================================================================= - path_type_ref_in_modpack = "util.path.types.Air" - path_type_raw_api_object = RawAPIObject(path_type_ref_in_modpack, - "Air", - api_objects, - path_types_location) - path_type_raw_api_object.set_filename("types") - path_type_raw_api_object.add_raw_parent(path_type_parent) - - pregen_converter_group.add_raw_api_object(path_type_raw_api_object) - pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) - - @staticmethod - def generate_resources( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate Attribute objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - resource_parent = "engine.util.resource.Resource" - resources_location = "data/util/resource/" - - # ======================================================================= - # Food - # ======================================================================= - food_ref_in_modpack = "util.resource.types.Food" - food_raw_api_object = RawAPIObject(food_ref_in_modpack, - "Food", api_objects, - resources_location) - food_raw_api_object.set_filename("types") - food_raw_api_object.add_raw_parent(resource_parent) - - pregen_converter_group.add_raw_api_object(food_raw_api_object) - pregen_nyan_objects.update({food_ref_in_modpack: food_raw_api_object}) - - food_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - food_name_ref_in_modpack = "util.attribute.types.Food.FoodName" - food_name_value = RawAPIObject(food_name_ref_in_modpack, "FoodName", - api_objects, resources_location) - food_name_value.set_filename("types") - food_name_value.add_raw_parent(name_value_parent) - food_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - food_name_ref_in_modpack) - food_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - - pregen_converter_group.add_raw_api_object(food_name_value) - pregen_nyan_objects.update({food_name_ref_in_modpack: food_name_value}) - - # ======================================================================= - # Wood - # ======================================================================= - wood_ref_in_modpack = "util.resource.types.Wood" - wood_raw_api_object = RawAPIObject(wood_ref_in_modpack, - "Wood", api_objects, - resources_location) - wood_raw_api_object.set_filename("types") - wood_raw_api_object.add_raw_parent(resource_parent) - - pregen_converter_group.add_raw_api_object(wood_raw_api_object) - pregen_nyan_objects.update({wood_ref_in_modpack: wood_raw_api_object}) - - wood_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - wood_name_ref_in_modpack = "util.attribute.types.Wood.WoodName" - wood_name_value = RawAPIObject(wood_name_ref_in_modpack, "WoodName", - api_objects, resources_location) - wood_name_value.set_filename("types") - wood_name_value.add_raw_parent(name_value_parent) - wood_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - wood_name_ref_in_modpack) - wood_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - - pregen_converter_group.add_raw_api_object(wood_name_value) - pregen_nyan_objects.update({wood_name_ref_in_modpack: wood_name_value}) - - # ======================================================================= - # Stone - # ======================================================================= - stone_ref_in_modpack = "util.resource.types.Stone" - stone_raw_api_object = RawAPIObject(stone_ref_in_modpack, - "Stone", api_objects, - resources_location) - stone_raw_api_object.set_filename("types") - stone_raw_api_object.add_raw_parent(resource_parent) - - pregen_converter_group.add_raw_api_object(stone_raw_api_object) - pregen_nyan_objects.update({stone_ref_in_modpack: stone_raw_api_object}) - - stone_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - stone_name_ref_in_modpack = "util.attribute.types.Stone.StoneName" - stone_name_value = RawAPIObject(stone_name_ref_in_modpack, "StoneName", - api_objects, resources_location) - stone_name_value.set_filename("types") - stone_name_value.add_raw_parent(name_value_parent) - stone_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - stone_name_ref_in_modpack) - stone_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - - pregen_converter_group.add_raw_api_object(stone_name_value) - pregen_nyan_objects.update({stone_name_ref_in_modpack: stone_name_value}) - - # ======================================================================= - # Gold - # ======================================================================= - gold_ref_in_modpack = "util.resource.types.Gold" - gold_raw_api_object = RawAPIObject(gold_ref_in_modpack, - "Gold", api_objects, - resources_location) - gold_raw_api_object.set_filename("types") - gold_raw_api_object.add_raw_parent(resource_parent) - - pregen_converter_group.add_raw_api_object(gold_raw_api_object) - pregen_nyan_objects.update({gold_ref_in_modpack: gold_raw_api_object}) - - gold_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - gold_name_ref_in_modpack = "util.attribute.types.Gold.GoldName" - gold_name_value = RawAPIObject(gold_name_ref_in_modpack, "GoldName", - api_objects, resources_location) - gold_name_value.set_filename("types") - gold_name_value.add_raw_parent(name_value_parent) - gold_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - gold_name_ref_in_modpack) - gold_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - - pregen_converter_group.add_raw_api_object(gold_name_value) - pregen_nyan_objects.update({gold_name_ref_in_modpack: gold_name_value}) - - # ======================================================================= - # Population Space - # ======================================================================= - resource_contingent_parent = "engine.util.resource.ResourceContingent" - - pop_ref_in_modpack = "util.resource.types.PopulationSpace" - pop_raw_api_object = RawAPIObject(pop_ref_in_modpack, - "PopulationSpace", api_objects, - resources_location) - pop_raw_api_object.set_filename("types") - pop_raw_api_object.add_raw_parent(resource_contingent_parent) - - pregen_converter_group.add_raw_api_object(pop_raw_api_object) - pregen_nyan_objects.update({pop_ref_in_modpack: pop_raw_api_object}) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - pop_name_ref_in_modpack = "util.attribute.types.PopulationSpace.PopulationSpaceName" - pop_name_value = RawAPIObject(pop_name_ref_in_modpack, "PopulationSpaceName", - api_objects, resources_location) - pop_name_value.set_filename("types") - pop_name_value.add_raw_parent(name_value_parent) - pop_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - pop_name_ref_in_modpack) - pop_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - pop_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - pop_raw_api_object.add_raw_member("min_amount", - 0, - resource_contingent_parent) - pop_raw_api_object.add_raw_member("max_amount", - 200, - resource_contingent_parent) - - pregen_converter_group.add_raw_api_object(pop_name_value) - pregen_nyan_objects.update({pop_name_ref_in_modpack: pop_name_value}) - - @staticmethod - def generate_death_condition( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate DeathCondition objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - # ======================================================================= - # Death condition - # ======================================================================= - logic_parent = "engine.util.logic.LogicElement" - literal_parent = "engine.util.logic.literal.Literal" - interval_parent = "engine.util.logic.literal.type.AttributeBelowValue" - literal_location = "data/util/logic/death/" - - death_ref_in_modpack = "util.logic.literal.death.StandardHealthDeathLiteral" - literal_raw_api_object = RawAPIObject(death_ref_in_modpack, - "StandardHealthDeathLiteral", - api_objects, - literal_location) - literal_raw_api_object.set_filename("death") - literal_raw_api_object.add_raw_parent(interval_parent) - - # Literal will not default to 'True' when it was fulfilled once - literal_raw_api_object.add_raw_member("only_once", False, logic_parent) - - # Scope - scope_forward_ref = ForwardRef(pregen_converter_group, - "util.logic.literal_scope.death.StandardHealthDeathScope") - literal_raw_api_object.add_raw_member("scope", - scope_forward_ref, - literal_parent) - - # Attribute - health_forward_ref = ForwardRef(pregen_converter_group, - "util.attribute.types.Health") - literal_raw_api_object.add_raw_member("attribute", - health_forward_ref, - interval_parent) - - # sidenote: Apparently this is actually HP<1 in Genie - # (https://youtu.be/FdBk8zGbE7U?t=7m16s) - literal_raw_api_object.add_raw_member("threshold", - 1, - interval_parent) - - pregen_converter_group.add_raw_api_object(literal_raw_api_object) - pregen_nyan_objects.update({death_ref_in_modpack: literal_raw_api_object}) - - # LiteralScope - scope_parent = "engine.util.logic.literal_scope.LiteralScope" - self_scope_parent = "engine.util.logic.literal_scope.type.Self" - - death_scope_ref_in_modpack = "util.logic.literal_scope.death.StandardHealthDeathScope" - scope_raw_api_object = RawAPIObject(death_scope_ref_in_modpack, - "StandardHealthDeathScope", - api_objects) - scope_location = ForwardRef(pregen_converter_group, death_ref_in_modpack) - scope_raw_api_object.set_location(scope_location) - scope_raw_api_object.add_raw_parent(self_scope_parent) - - scope_diplomatic_stances = [api_objects["engine.util.diplomatic_stance.type.Self"]] - scope_raw_api_object.add_raw_member("stances", - scope_diplomatic_stances, - scope_parent) - - pregen_converter_group.add_raw_api_object(scope_raw_api_object) - pregen_nyan_objects.update({death_scope_ref_in_modpack: scope_raw_api_object}) - - # ======================================================================= - # Garrison empty condition - # ======================================================================= - logic_parent = "engine.util.logic.LogicElement" - literal_parent = "engine.util.logic.literal.Literal" - interval_parent = "engine.util.logic.literal.type.AttributeBelowValue" - literal_location = "data/util/logic/garrison_empty/" - - garrison_literal_ref_in_modpack = "util.logic.literal.garrison.BuildingDamageEmpty" - literal_raw_api_object = RawAPIObject(garrison_literal_ref_in_modpack, - "BuildingDamageEmptyLiteral", - api_objects, - literal_location) - literal_raw_api_object.set_filename("garrison_empty") - literal_raw_api_object.add_raw_parent(interval_parent) - - # Literal will not default to 'True' when it was fulfilled once - literal_raw_api_object.add_raw_member("only_once", False, logic_parent) - - # Scope - scope_forward_ref = ForwardRef(pregen_converter_group, - "util.logic.literal_scope.garrison.BuildingDamageEmptyScope") - literal_raw_api_object.add_raw_member("scope", - scope_forward_ref, - literal_parent) - - # Attribute - health_forward_ref = ForwardRef(pregen_converter_group, - "util.attribute.types.Health") - literal_raw_api_object.add_raw_member("attribute", - health_forward_ref, - interval_parent) - - # Threshhold - literal_raw_api_object.add_raw_member("threshold", - 0.2, - interval_parent) - - pregen_converter_group.add_raw_api_object(literal_raw_api_object) - pregen_nyan_objects.update({garrison_literal_ref_in_modpack: literal_raw_api_object}) - - # LiteralScope - scope_parent = "engine.util.logic.literal_scope.LiteralScope" - self_scope_parent = "engine.util.logic.literal_scope.type.Self" - - garrison_scope_ref_in_modpack = "util.logic.literal_scope.garrison.BuildingDamageEmptyScope" - scope_raw_api_object = RawAPIObject(garrison_scope_ref_in_modpack, - "BuildingDamageEmptyScope", - api_objects) - scope_location = ForwardRef(pregen_converter_group, garrison_literal_ref_in_modpack) - scope_raw_api_object.set_location(scope_location) - scope_raw_api_object.add_raw_parent(self_scope_parent) - - scope_diplomatic_stances = [api_objects["engine.util.diplomatic_stance.type.Self"]] - scope_raw_api_object.add_raw_member("stances", - scope_diplomatic_stances, - scope_parent) - - pregen_converter_group.add_raw_api_object(scope_raw_api_object) - pregen_nyan_objects.update({garrison_scope_ref_in_modpack: scope_raw_api_object}) + generate_activities = staticmethod(generate_activities) + generate_attributes = staticmethod(generate_attributes) + generate_death_condition = staticmethod(generate_death_condition) + generate_garrison_empty_condition = staticmethod(generate_garrison_empty_condition) + generate_resources = staticmethod(generate_resources) + generate_diplomatic_stances = staticmethod(generate_diplomatic_stances) + generate_team_property = staticmethod(generate_team_property) + generate_entity_types = staticmethod(generate_entity_types) + generate_effect_types = staticmethod(generate_effect_types) + generate_exchange_objects = staticmethod(generate_exchange_objects) + generate_formation_types = staticmethod(generate_formation_objects) + generate_language_objects = staticmethod(generate_language_objects) + generate_modifiers = staticmethod(generate_modifiers) + generate_terrain_types = staticmethod(generate_terrain_types) + generate_path_types = staticmethod(generate_path_types) + generate_misc_effect_objects = staticmethod(generate_misc_effect_objects) From 3abf1ea51c70b22ae8b794a32ece1bed195cd968 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 31 May 2025 04:19:04 +0200 Subject: [PATCH 119/163] convert: Refactor AoCProcessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../conversion/aoc/main/CMakeLists.txt | 7 + .../processor/conversion/aoc/main/__init__.py | 5 + .../aoc/main/extract/CMakeLists.txt | 11 + .../conversion/aoc/main/extract/__init__.py | 5 + .../conversion/aoc/main/extract/civ.py | 41 + .../conversion/aoc/main/extract/connection.py | 95 ++ .../aoc/main/extract/effect_bundle.py | 98 ++ .../conversion/aoc/main/extract/graphics.py | 43 + .../conversion/aoc/main/extract/sound.py | 30 + .../conversion/aoc/main/extract/tech.py | 34 + .../conversion/aoc/main/extract/terrain.py | 53 + .../conversion/aoc/main/extract/unit.py | 50 + .../conversion/aoc/main/groups/CMakeLists.txt | 11 + .../conversion/aoc/main/groups/__init__.py | 5 + .../aoc/main/groups/ambient_group.py | 32 + .../aoc/main/groups/building_line.py | 122 ++ .../conversion/aoc/main/groups/civ_group.py | 32 + .../conversion/aoc/main/groups/tech_group.py | 177 +++ .../aoc/main/groups/terrain_group.py | 38 + .../conversion/aoc/main/groups/unit_line.py | 132 ++ .../aoc/main/groups/variant_group.py | 33 + .../aoc/main/groups/villager_group.py | 69 + .../conversion/aoc/main/link/CMakeLists.txt | 11 + .../conversion/aoc/main/link/__init__.py | 5 + .../aoc/main/link/building_upgrade.py | 42 + .../conversion/aoc/main/link/civ_unique.py | 49 + .../conversion/aoc/main/link/creatable.py | 43 + .../conversion/aoc/main/link/garrison.py | 125 ++ .../conversion/aoc/main/link/gather.py | 35 + .../conversion/aoc/main/link/repairable.py | 52 + .../conversion/aoc/main/link/researchable.py | 28 + .../conversion/aoc/main/link/trade_post.py | 40 + .../processor/conversion/aoc/processor.py | 1372 ++--------------- 34 files changed, 1651 insertions(+), 1275 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/main/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/main/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/main/extract/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/main/extract/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/main/extract/civ.py create mode 100644 openage/convert/processor/conversion/aoc/main/extract/connection.py create mode 100644 openage/convert/processor/conversion/aoc/main/extract/effect_bundle.py create mode 100644 openage/convert/processor/conversion/aoc/main/extract/graphics.py create mode 100644 openage/convert/processor/conversion/aoc/main/extract/sound.py create mode 100644 openage/convert/processor/conversion/aoc/main/extract/tech.py create mode 100644 openage/convert/processor/conversion/aoc/main/extract/terrain.py create mode 100644 openage/convert/processor/conversion/aoc/main/extract/unit.py create mode 100644 openage/convert/processor/conversion/aoc/main/groups/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/main/groups/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/main/groups/ambient_group.py create mode 100644 openage/convert/processor/conversion/aoc/main/groups/building_line.py create mode 100644 openage/convert/processor/conversion/aoc/main/groups/civ_group.py create mode 100644 openage/convert/processor/conversion/aoc/main/groups/tech_group.py create mode 100644 openage/convert/processor/conversion/aoc/main/groups/terrain_group.py create mode 100644 openage/convert/processor/conversion/aoc/main/groups/unit_line.py create mode 100644 openage/convert/processor/conversion/aoc/main/groups/variant_group.py create mode 100644 openage/convert/processor/conversion/aoc/main/groups/villager_group.py create mode 100644 openage/convert/processor/conversion/aoc/main/link/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/main/link/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/main/link/building_upgrade.py create mode 100644 openage/convert/processor/conversion/aoc/main/link/civ_unique.py create mode 100644 openage/convert/processor/conversion/aoc/main/link/creatable.py create mode 100644 openage/convert/processor/conversion/aoc/main/link/garrison.py create mode 100644 openage/convert/processor/conversion/aoc/main/link/gather.py create mode 100644 openage/convert/processor/conversion/aoc/main/link/repairable.py create mode 100644 openage/convert/processor/conversion/aoc/main/link/researchable.py create mode 100644 openage/convert/processor/conversion/aoc/main/link/trade_post.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index a4362f49e6..856eb016da 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -21,6 +21,7 @@ add_subdirectory(ability) add_subdirectory(auxiliary) add_subdirectory(civ) add_subdirectory(effect) +add_subdirectory(main) add_subdirectory(media) add_subdirectory(modifier) add_subdirectory(modpack) diff --git a/openage/convert/processor/conversion/aoc/main/CMakeLists.txt b/openage/convert/processor/conversion/aoc/main/CMakeLists.txt new file mode 100644 index 0000000000..8de84d720c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/CMakeLists.txt @@ -0,0 +1,7 @@ +add_py_modules( + __init__.py +) + +add_subdirectory(extract) +add_subdirectory(groups) +add_subdirectory(link) diff --git a/openage/convert/processor/conversion/aoc/main/__init__.py b/openage/convert/processor/conversion/aoc/main/__init__.py new file mode 100644 index 0000000000..c85d808897 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Routines for the main AoC conversion process. +""" diff --git a/openage/convert/processor/conversion/aoc/main/extract/CMakeLists.txt b/openage/convert/processor/conversion/aoc/main/extract/CMakeLists.txt new file mode 100644 index 0000000000..56a7a88521 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/CMakeLists.txt @@ -0,0 +1,11 @@ +add_py_modules( + __init__.py + civ.py + connection.py + effect_bundle.py + graphics.py + sound.py + tech.py + terrain.py + unit.py +) diff --git a/openage/convert/processor/conversion/aoc/main/extract/__init__.py b/openage/convert/processor/conversion/aoc/main/extract/__init__.py new file mode 100644 index 0000000000..b88655a0c8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract AoC data from the game dataset and prepares it for conversion. +""" diff --git a/openage/convert/processor/conversion/aoc/main/extract/civ.py b/openage/convert/processor/conversion/aoc/main/extract/civ.py new file mode 100644 index 0000000000..bdaa5de1f7 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/civ.py @@ -0,0 +1,41 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract civs from the AoC data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_civ import GenieCivilizationObject + +if typing.TYPE_CHECKING: + from ......value_object.read.value_members import ArrayMember + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_genie_civs( + gamespec: ArrayMember, + full_data_set: GenieObjectContainer +) -> None: + """ + Extract civs from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + raw_civs = gamespec[0]["civs"].value + + index = 0 + for raw_civ in raw_civs: + civ_id = index + + civ_members = raw_civ.value + units_member = civ_members.pop("units") + units_member = units_member.get_container("id0") + + civ_members.update({"units": units_member}) + + civ = GenieCivilizationObject(civ_id, full_data_set, members=civ_members) + full_data_set.genie_civs.update({civ.get_id(): civ}) + + index += 1 diff --git a/openage/convert/processor/conversion/aoc/main/extract/connection.py b/openage/convert/processor/conversion/aoc/main/extract/connection.py new file mode 100644 index 0000000000..b779b3d002 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/connection.py @@ -0,0 +1,95 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract connection objects from the AoC data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_connection import GenieAgeConnection, \ + GenieBuildingConnection, GenieUnitConnection, GenieTechConnection + +if typing.TYPE_CHECKING: + from ......value_object.read.value_members import ArrayMember + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_age_connections(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract age connections from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + raw_connections = gamespec[0]["age_connections"].value + + for raw_connection in raw_connections: + age_id = raw_connection["id"].value + connection_members = raw_connection.value + + connection = GenieAgeConnection(age_id, full_data_set, members=connection_members) + full_data_set.age_connections.update({connection.get_id(): connection}) + + +@staticmethod +def extract_building_connections( + gamespec: ArrayMember, + full_data_set: GenieObjectContainer +) -> None: + """ + Extract building connections from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + raw_connections = gamespec[0]["building_connections"].value + + for raw_connection in raw_connections: + building_id = raw_connection["id"].value + connection_members = raw_connection.value + + connection = GenieBuildingConnection(building_id, full_data_set, + members=connection_members) + full_data_set.building_connections.update({connection.get_id(): connection}) + + +@staticmethod +def extract_unit_connections( + gamespec: ArrayMember, + full_data_set: GenieObjectContainer +) -> None: + """ + Extract unit connections from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + raw_connections = gamespec[0]["unit_connections"].value + + for raw_connection in raw_connections: + unit_id = raw_connection["id"].value + connection_members = raw_connection.value + + connection = GenieUnitConnection(unit_id, full_data_set, members=connection_members) + full_data_set.unit_connections.update({connection.get_id(): connection}) + + +@staticmethod +def extract_tech_connections( + gamespec: ArrayMember, + full_data_set: GenieObjectContainer +) -> None: + """ + Extract tech connections from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + raw_connections = gamespec[0]["tech_connections"].value + + for raw_connection in raw_connections: + tech_id = raw_connection["id"].value + connection_members = raw_connection.value + + connection = GenieTechConnection(tech_id, full_data_set, members=connection_members) + full_data_set.tech_connections.update({connection.get_id(): connection}) diff --git a/openage/convert/processor/conversion/aoc/main/extract/effect_bundle.py b/openage/convert/processor/conversion/aoc/main/extract/effect_bundle.py new file mode 100644 index 0000000000..839e0c363e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/effect_bundle.py @@ -0,0 +1,98 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract effect objects from the AoC data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_effect import GenieEffectObject, GenieEffectBundle + +if typing.TYPE_CHECKING: + from ......value_object.read.value_members import ArrayMember + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_genie_effect_bundles( + gamespec: ArrayMember, + full_data_set: GenieObjectContainer +) -> None: + """ + Extract effects and effect bundles from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: ...dataformat.value_members.ArrayMember + """ + raw_effect_bundles = gamespec[0]["effect_bundles"].value + + index_bundle = 0 + for raw_effect_bundle in raw_effect_bundles: + bundle_id = index_bundle + + # call hierarchy: effect_bundle->effects + raw_effects = raw_effect_bundle["effects"].value + + effects = {} + + index_effect = 0 + for raw_effect in raw_effects: + effect_id = index_effect + effect_members = raw_effect.value + + effect = GenieEffectObject(effect_id, bundle_id, full_data_set, + members=effect_members) + + effects.update({effect_id: effect}) + + index_effect += 1 + + # Pass everything to the bundle + effect_bundle_members = raw_effect_bundle.value + # Remove effects we store them as separate objects + effect_bundle_members.pop("effects") + + bundle = GenieEffectBundle(bundle_id, effects, full_data_set, + members=effect_bundle_members) + full_data_set.genie_effect_bundles.update({bundle.get_id(): bundle}) + + index_bundle += 1 + + +def sanitize_effect_bundles(full_data_set: GenieObjectContainer) -> None: + """ + Remove garbage data from effect bundles. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + effect_bundles = full_data_set.genie_effect_bundles + + for bundle in effect_bundles.values(): + sanitized_effects = {} + + effects = bundle.get_effects() + + index = 0 + for effect in effects: + effect_type = effect["type_id"].value + if effect_type < 0: + # Effect has no type + continue + + if effect_type == 3: + if effect["attr_b"].value < 0: + # Upgrade to invalid unit + continue + + if effect_type == 102: + if effect["attr_d"].value < 0: + # Tech disable effect with no tech id specified + continue + + sanitized_effects.update({index: effect}) + index += 1 + + bundle.effects = sanitized_effects + bundle.sanitized = True diff --git a/openage/convert/processor/conversion/aoc/main/extract/graphics.py b/openage/convert/processor/conversion/aoc/main/extract/graphics.py new file mode 100644 index 0000000000..283fe524fa --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/graphics.py @@ -0,0 +1,43 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract graphics from the AoC data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_graphic import GenieGraphic + +if typing.TYPE_CHECKING: + from ......value_object.read.value_members import ArrayMember + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_genie_graphics(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract graphic definitions from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + raw_graphics = gamespec[0]["graphics"].value + + for raw_graphic in raw_graphics: + # Can be ignored if there is no filename associated + filename = raw_graphic["filename"].value + if not filename: + continue + + graphic_id = raw_graphic["graphic_id"].value + graphic_members = raw_graphic.value + + graphic = GenieGraphic(graphic_id, full_data_set, members=graphic_members) + slp_id = raw_graphic["slp_id"].value + if str(slp_id) not in full_data_set.existing_graphics: + graphic.exists = False + + full_data_set.genie_graphics.update({graphic.get_id(): graphic}) + + # Detect subgraphics + for genie_graphic in full_data_set.genie_graphics.values(): + genie_graphic.detect_subgraphics() diff --git a/openage/convert/processor/conversion/aoc/main/extract/sound.py b/openage/convert/processor/conversion/aoc/main/extract/sound.py new file mode 100644 index 0000000000..63616c5c96 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/sound.py @@ -0,0 +1,30 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract sounds from the AoC data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_sound import GenieSound + +if typing.TYPE_CHECKING: + from ......value_object.read.value_members import ArrayMember + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_genie_sounds(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract sound definitions from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + raw_sounds = gamespec[0]["sounds"].value + + for raw_sound in raw_sounds: + sound_id = raw_sound["sound_id"].value + sound_members = raw_sound.value + + sound = GenieSound(sound_id, full_data_set, members=sound_members) + full_data_set.genie_sounds.update({sound.get_id(): sound}) diff --git a/openage/convert/processor/conversion/aoc/main/extract/tech.py b/openage/convert/processor/conversion/aoc/main/extract/tech.py new file mode 100644 index 0000000000..a48bda3da4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/tech.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract techs from the AoC data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_tech import GenieTechObject + +if typing.TYPE_CHECKING: + from ......value_object.read.value_members import ArrayMember + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_genie_techs(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract techs from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: ...dataformat.value_members.ArrayMember + """ + # Techs are stored as "researches" + raw_techs = gamespec[0]["researches"].value + + index = 0 + for raw_tech in raw_techs: + tech_id = index + tech_members = raw_tech.value + + tech = GenieTechObject(tech_id, full_data_set, members=tech_members) + full_data_set.genie_techs.update({tech.get_id(): tech}) + + index += 1 diff --git a/openage/convert/processor/conversion/aoc/main/extract/terrain.py b/openage/convert/processor/conversion/aoc/main/extract/terrain.py new file mode 100644 index 0000000000..4d9960d583 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/terrain.py @@ -0,0 +1,53 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract terrains from the AoC data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_terrain import GenieTerrainObject, \ + GenieTerrainRestriction + +if typing.TYPE_CHECKING: + from ......value_object.read.value_members import ArrayMember + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_genie_terrains(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract terrains from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + raw_terrains = gamespec[0]["terrains"].value + + for index, raw_terrain in enumerate(raw_terrains): + terrain_index = index + terrain_members = raw_terrain.value + + terrain = GenieTerrainObject(terrain_index, full_data_set, members=terrain_members) + full_data_set.genie_terrains.update({terrain.get_id(): terrain}) + + +def extract_genie_restrictions( + gamespec: ArrayMember, + full_data_set: GenieObjectContainer +) -> None: + """ + Extract terrain restrictions from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + raw_restrictions = gamespec[0]["terrain_restrictions"].value + + for index, raw_restriction in enumerate(raw_restrictions): + restriction_index = index + restriction_members = raw_restriction.value + + restriction = GenieTerrainRestriction(restriction_index, + full_data_set, + members=restriction_members) + full_data_set.genie_terrain_restrictions.update({restriction.get_id(): restriction}) diff --git a/openage/convert/processor/conversion/aoc/main/extract/unit.py b/openage/convert/processor/conversion/aoc/main/extract/unit.py new file mode 100644 index 0000000000..14d3242229 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/extract/unit.py @@ -0,0 +1,50 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract units from the AoC data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieUnitObject + +if typing.TYPE_CHECKING: + from ......value_object.read.value_members import ArrayMember + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_genie_units(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract units from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: ...dataformat.value_members.ArrayMember + """ + # Units are stored in the civ container. + # All civs point to the same units (?) except for Gaia which has more. + # Gaia also seems to have the most units, so we only read from Gaia + raw_units = gamespec[0]["civs"][0]["units"].value + + # Unit headers store the things units can do + raw_unit_headers = gamespec[0]["unit_headers"].value + + for raw_unit in raw_units: + unit_id = raw_unit["id0"].value + unit_members = raw_unit.value + + # Turn attack and armor into containers to make diffing work + if "attacks" in unit_members.keys(): + attacks_member = unit_members.pop("attacks") + attacks_member = attacks_member.get_container("type_id") + armors_member = unit_members.pop("armors") + armors_member = armors_member.get_container("type_id") + + unit_members.update({"attacks": attacks_member}) + unit_members.update({"armors": armors_member}) + + unit = GenieUnitObject(unit_id, full_data_set, members=unit_members) + full_data_set.genie_units.update({unit.get_id(): unit}) + + # Commands + unit_commands = raw_unit_headers[unit_id]["unit_commands"] + unit.add_member(unit_commands) diff --git a/openage/convert/processor/conversion/aoc/main/groups/CMakeLists.txt b/openage/convert/processor/conversion/aoc/main/groups/CMakeLists.txt new file mode 100644 index 0000000000..140789626c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/CMakeLists.txt @@ -0,0 +1,11 @@ +add_py_modules( + __init__.py + ambient_group.py + building_line.py + civ_group.py + tech_group.py + terrain_group.py + unit_line.py + variant_group.py + villager_group.py +) diff --git a/openage/convert/processor/conversion/aoc/main/groups/__init__.py b/openage/convert/processor/conversion/aoc/main/groups/__init__.py new file mode 100644 index 0000000000..1246e6bf5f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create lines and groups fron extracted AoC data. +""" diff --git a/openage/convert/processor/conversion/aoc/main/groups/ambient_group.py b/openage/convert/processor/conversion/aoc/main/groups/ambient_group.py new file mode 100644 index 0000000000..40ca29bce6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/ambient_group.py @@ -0,0 +1,32 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create ambient groups from genie units. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieAmbientGroup +from ......value_object.conversion.aoc.internal_nyan_names import AMBIENT_GROUP_LOOKUPS + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_ambient_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create ambient groups, mostly for resources and scenery. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + ambient_ids = AMBIENT_GROUP_LOOKUPS.keys() + genie_units = full_data_set.genie_units + + for ambient_id in ambient_ids: + ambient_group = GenieAmbientGroup(ambient_id, full_data_set) + ambient_group.add_unit(genie_units[ambient_id]) + full_data_set.ambient_groups.update({ambient_group.get_id(): ambient_group}) + full_data_set.unit_ref.update({ambient_id: ambient_group}) diff --git a/openage/convert/processor/conversion/aoc/main/groups/building_line.py b/openage/convert/processor/conversion/aoc/main/groups/building_line.py new file mode 100644 index 0000000000..ccbe6dbf6c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/building_line.py @@ -0,0 +1,122 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create building lines from genie buildings. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, \ + GenieStackBuildingGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_building_lines(full_data_set: GenieObjectContainer) -> None: + """ + Establish building lines, based on information in the building connections. + Because of how Genie building lines work, this will only find the first + building in the line. Subsequent buildings in the line have to be determined + by effects in AgeUpTechs. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + building_connections = full_data_set.building_connections + + for connection in building_connections.values(): + building_id = connection["id"].value + building = full_data_set.genie_units[building_id] + previous_building_id = None + stack_building = False + + # Buildings have no actual lines, so we use + # their unit ID as the line ID. + line_id = building_id + + # Check if we have to create a GenieStackBuildingGroup + if building.has_member("stack_unit_id") and \ + building["stack_unit_id"].value > -1: + stack_building = True + + if building.has_member("head_unit_id") and \ + building["head_unit_id"].value > -1: + # we don't care about head units because we process + # them with their stack unit + continue + + # Check if the building is part of an existing line. + # To do this, we look for connected techs and + # check if any tech has an upgrade effect. + connected_types = connection["other_connections"].value + connected_tech_indices = [] + for index, _ in enumerate(connected_types): + connected_type = connected_types[index]["other_connection"].value + if connected_type == 3: + # 3 == Tech + connected_tech_indices.append(index) + + connected_ids = connection["other_connected_ids"].value + + for index in connected_tech_indices: + connected_tech_id = connected_ids[index].value + connected_tech = full_data_set.genie_techs[connected_tech_id] + effect_bundle_id = connected_tech["tech_effect_id"].value + effect_bundle = full_data_set.genie_effect_bundles[effect_bundle_id] + + upgrade_effects = effect_bundle.get_effects(effect_type=3) + + if len(upgrade_effects) == 0: + continue + + # Search upgrade effects for the line_id + for upgrade in upgrade_effects: + upgrade_source = upgrade["attr_a"].value + upgrade_target = upgrade["attr_b"].value + + # Check if the upgrade target is correct + if upgrade_target == building_id: + # Line id is the source building id + line_id = upgrade_source + break + + else: + # If no upgrade was found, then search remaining techs + continue + + # Find the previous building + for c_index, _ in enumerate(connected_types): + connected_type = connected_types[c_index]["other_connection"].value + if connected_type == 1: + # 1 == Building + connected_index = c_index + break + + else: + raise RuntimeError(f"Building {building_id} is not first in line, but no " + "previous building could be found in other_connections") + + previous_building_id = connected_ids[connected_index].value + break + + if line_id == building_id: + # First building in line + if stack_building: + stack_unit_id = building["stack_unit_id"].value + building_line = GenieStackBuildingGroup(stack_unit_id, line_id, full_data_set) + + else: + building_line = GenieBuildingLineGroup(line_id, full_data_set) + + full_data_set.building_lines.update({building_line.get_id(): building_line}) + building_line.add_unit(building, after=previous_building_id) + full_data_set.unit_ref.update({building_id: building_line}) + + else: + # It's an upgraded building + building_line = full_data_set.building_lines[line_id] + building_line.add_unit(building, after=previous_building_id) + full_data_set.unit_ref.update({building_id: building_line}) diff --git a/openage/convert/processor/conversion/aoc/main/groups/civ_group.py b/openage/convert/processor/conversion/aoc/main/groups/civ_group.py new file mode 100644 index 0000000000..bd562a168f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/civ_group.py @@ -0,0 +1,32 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create civ groups from genie civs. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_civ_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create civilization groups from civ objects. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + civ_objects = full_data_set.genie_civs + + for index in range(len(civ_objects)): + civ_id = index + + civ_group = GenieCivilizationGroup(civ_id, full_data_set) + full_data_set.civ_groups.update({civ_group.get_id(): civ_group}) + + index += 1 diff --git a/openage/convert/processor/conversion/aoc/main/groups/tech_group.py b/openage/convert/processor/conversion/aoc/main/groups/tech_group.py new file mode 100644 index 0000000000..ead5ac3111 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/tech_group.py @@ -0,0 +1,177 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create tech groups from genie techs. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_tech import AgeUpgrade, BuildingUnlock, \ + BuildingLineUpgrade, CivBonus, InitiatedTech, StatUpgrade, UnitLineUpgrade, \ + UnitUnlock + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_tech_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create techs from tech connections and unit upgrades/unlocks + from unit connections. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + tech_connections = full_data_set.tech_connections + + # In tech connection are age ups, building unlocks/upgrades and stat upgrades + for connection in tech_connections.values(): + connected_buildings = connection["buildings"].value + tech_id = connection["id"].value + tech = full_data_set.genie_techs[tech_id] + + effect_id = tech["tech_effect_id"].value + if effect_id < 0: + continue + + tech_effects = full_data_set.genie_effect_bundles[effect_id] + + # Check if the tech is an age upgrade + tech_found = False + resource_effects = tech_effects.get_effects(effect_type=1) + for effect in resource_effects: + # Resource ID 6: Current Age + if effect["attr_a"].value != 6: + continue + + age_id = effect["attr_b"].value + age_up = AgeUpgrade(tech_id, age_id, full_data_set) + full_data_set.tech_groups.update({age_up.get_id(): age_up}) + full_data_set.age_upgrades.update({age_up.get_id(): age_up}) + tech_found = True + break + + if tech_found: + continue + + if len(connected_buildings) > 0: + # Building unlock or upgrade + unlock_effects = tech_effects.get_effects(effect_type=2) + upgrade_effects = tech_effects.get_effects(effect_type=2) + if len(unlock_effects) > 0: + unlock = unlock_effects[0] + unlock_id = unlock["attr_a"].value + + building_unlock = BuildingUnlock(tech_id, unlock_id, full_data_set) + full_data_set.tech_groups.update( + {building_unlock.get_id(): building_unlock} + ) + full_data_set.building_unlocks.update( + {building_unlock.get_id(): building_unlock} + ) + continue + + if len(upgrade_effects) > 0: + upgrade = upgrade_effects[0] + line_id = upgrade["attr_a"].value + upgrade_id = upgrade["attr_b"].value + + building_upgrade = BuildingLineUpgrade( + tech_id, + line_id, + upgrade_id, + full_data_set + ) + full_data_set.tech_groups.update( + {building_upgrade.get_id(): building_upgrade} + ) + full_data_set.building_upgrades.update( + {building_upgrade.get_id(): building_upgrade} + ) + continue + + # Create a stat upgrade for other techs + stat_up = StatUpgrade(tech_id, full_data_set) + full_data_set.tech_groups.update({stat_up.get_id(): stat_up}) + full_data_set.stat_upgrades.update({stat_up.get_id(): stat_up}) + + # Unit upgrades and unlocks are stored in unit connections + unit_connections = full_data_set.unit_connections + for connection in unit_connections.values(): + unit_id = connection["id"].value + required_research_id = connection["required_research"].value + enabling_research_id = connection["enabling_research"].value + line_mode = connection["line_mode"].value + line_id = full_data_set.unit_ref[unit_id].get_id() + + if required_research_id == -1 and enabling_research_id == -1: + # Unit is unlocked from the start + continue + + if line_mode == 2: + # Unit is first in line, there should be an unlock tech id + # This is usually the enabling tech id + unlock_tech_id = enabling_research_id + if unlock_tech_id == -1: + # Battle elephant is a curious exception wher it's the required tech id + unlock_tech_id = required_research_id + + unit_unlock = UnitUnlock(unlock_tech_id, line_id, full_data_set) + full_data_set.tech_groups.update({unit_unlock.get_id(): unit_unlock}) + full_data_set.unit_unlocks.update({unit_unlock.get_id(): unit_unlock}) + + elif line_mode == 3: + # Units further down the line receive line upgrades + unit_upgrade = UnitLineUpgrade(required_research_id, line_id, + unit_id, full_data_set) + full_data_set.tech_groups.update({unit_upgrade.get_id(): unit_upgrade}) + full_data_set.unit_upgrades.update({unit_upgrade.get_id(): unit_upgrade}) + + # Initiated techs are stored with buildings + genie_units = full_data_set.genie_units + + for genie_unit in genie_units.values(): + if not genie_unit.has_member("research_id"): + continue + + building_id = genie_unit["id0"].value + initiated_tech_id = genie_unit["research_id"].value + + if initiated_tech_id == -1: + continue + + if building_id not in full_data_set.building_lines.keys(): + # Skips upgraded buildings (which initiate the same techs) + continue + + initiated_tech = InitiatedTech(initiated_tech_id, building_id, full_data_set) + full_data_set.tech_groups.update({initiated_tech.get_id(): initiated_tech}) + full_data_set.initiated_techs.update({initiated_tech.get_id(): initiated_tech}) + + # Civ boni have to be aquired from techs + # Civ boni = ONLY passive boni (not unit unlocks, unit upgrades or team bonus) + genie_techs = full_data_set.genie_techs + + for index, _ in enumerate(genie_techs): + tech_id = index + + # Civ ID must be positive and non-zero + civ_id = genie_techs[index]["civilization_id"].value + if civ_id <= 0: + continue + + # Passive boni are not researched anywhere + research_location_id = genie_techs[index]["research_location_id"].value + if research_location_id > 0: + continue + + # Passive boni are not available in full tech mode + full_tech_mode = genie_techs[index]["full_tech_mode"].value + if full_tech_mode: + continue + + civ_bonus = CivBonus(tech_id, civ_id, full_data_set) + full_data_set.tech_groups.update({civ_bonus.get_id(): civ_bonus}) + full_data_set.civ_boni.update({civ_bonus.get_id(): civ_bonus}) diff --git a/openage/convert/processor/conversion/aoc/main/groups/terrain_group.py b/openage/convert/processor/conversion/aoc/main/groups/terrain_group.py new file mode 100644 index 0000000000..3c2d3ddd4a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/terrain_group.py @@ -0,0 +1,38 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create terrain groups from genie terrains. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_terrain import GenieTerrainGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_terrain_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create terrain groups. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + terrains = full_data_set.genie_terrains.values() + + for terrain in terrains: + slp_id = terrain["slp_id"].value + replacement_id = terrain["terrain_replacement_id"].value + + if slp_id == -1 and replacement_id == -1: + # No graphics and no graphics replacement means this terrain is unused + continue + + enabled = terrain["enabled"].value + + if enabled: + terrain_group = GenieTerrainGroup(terrain.get_id(), full_data_set) + full_data_set.terrain_groups.update({terrain.get_id(): terrain_group}) diff --git a/openage/convert/processor/conversion/aoc/main/groups/unit_line.py b/openage/convert/processor/conversion/aoc/main/groups/unit_line.py new file mode 100644 index 0000000000..c7dd1db5b4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/unit_line.py @@ -0,0 +1,132 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create unit lines from genie units. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup, \ + GenieUnitTransformGroup, GenieMonkGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_unit_lines(full_data_set: GenieObjectContainer) -> None: + """ + Sort units into lines, based on information in the unit connections. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + unit_connections = full_data_set.unit_connections + + # First only handle the line heads (firstunits in a line) + for connection in unit_connections.values(): + unit_id = connection["id"].value + unit = full_data_set.genie_units[unit_id] + line_mode = connection["line_mode"].value + + if line_mode != 2: + # It's an upgrade. Skip and handle later + continue + + # Check for special cases first + if unit.has_member("transform_unit_id")\ + and unit["transform_unit_id"].value > -1: + # Trebuchet + unit_line = GenieUnitTransformGroup(unit_id, unit_id, full_data_set) + full_data_set.transform_groups.update({unit_line.get_id(): unit_line}) + + elif unit_id == 125: + # Monks + # Switch to monk with relic is hardcoded :( + unit_line = GenieMonkGroup(unit_id, unit_id, 286, full_data_set) + full_data_set.monk_groups.update({unit_line.get_id(): unit_line}) + + elif unit.has_member("task_group")\ + and unit["task_group"].value > 0: + # Villager + # done somewhere else because they are special^TM + continue + + else: + # Normal units + unit_line = GenieUnitLineGroup(unit_id, full_data_set) + + unit_line.add_unit(unit) + full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) + full_data_set.unit_ref.update({unit_id: unit_line}) + + # Second, handle all upgraded units + for connection in unit_connections.values(): + unit_id = connection["id"].value + unit = full_data_set.genie_units[unit_id] + line_mode = connection["line_mode"].value + + if line_mode != 3: + # This unit is not an upgrade and was handled in the last for-loop + continue + + # Search other_connections for the previous unit in line + connected_types = connection["other_connections"].value + for index, _ in enumerate(connected_types): + connected_type = connected_types[index]["other_connection"].value + if connected_type == 2: + # 2 == Unit + connected_index = index + break + + else: + raise RuntimeError(f"Unit {unit_id} is not first in line, but no previous " + "unit can be found in other_connections") + + connected_ids = connection["other_connected_ids"].value + previous_unit_id = connected_ids[connected_index].value + + # Search for the first unit ID in the line recursively + previous_id = previous_unit_id + previous_connection = unit_connections[previous_unit_id] + while previous_connection["line_mode"] != 2: + if previous_id in full_data_set.unit_ref.keys(): + # Short-circuit here, if we the previous unit was already handled + break + + connected_types = previous_connection["other_connections"].value + connected_index = -1 + for index, _ in enumerate(connected_types): + connected_type = connected_types[index]["other_connection"].value + if connected_type == 2: + # 2 == Unit + connected_index = index + break + + connected_ids = previous_connection["other_connected_ids"].value + previous_id = connected_ids[connected_index].value + previous_connection = unit_connections[previous_id] + + unit_line = full_data_set.unit_ref[previous_id] + unit_line.add_unit(unit, after=previous_unit_id) + full_data_set.unit_ref.update({unit_id: unit_line}) + + +@staticmethod +def create_extra_unit_lines(full_data_set: GenieObjectContainer) -> None: + """ + Create additional units that are not in the unit connections. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + extra_units = (48, 65, 594, 833) # Wildlife + + for unit_id in extra_units: + unit_line = GenieUnitLineGroup(unit_id, full_data_set) + unit_line.add_unit(full_data_set.genie_units[unit_id]) + full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) + full_data_set.unit_ref.update({unit_id: unit_line}) diff --git a/openage/convert/processor/conversion/aoc/main/groups/variant_group.py b/openage/convert/processor/conversion/aoc/main/groups/variant_group.py new file mode 100644 index 0000000000..225b9e2dae --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/variant_group.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create variant groups from genie units. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieVariantGroup +from ......value_object.conversion.aoc.internal_nyan_names import VARIANT_GROUP_LOOKUPS + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_variant_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create variant groups. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + variants = VARIANT_GROUP_LOOKUPS + + for group_id, variant in variants.items(): + variant_group = GenieVariantGroup(group_id, full_data_set) + full_data_set.variant_groups.update({variant_group.get_id(): variant_group}) + + for variant_id in variant[2]: + variant_group.add_unit(full_data_set.genie_units[variant_id]) + full_data_set.unit_ref.update({variant_id: variant_group}) diff --git a/openage/convert/processor/conversion/aoc/main/groups/villager_group.py b/openage/convert/processor/conversion/aoc/main/groups/villager_group.py new file mode 100644 index 0000000000..317bc86ef0 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/villager_group.py @@ -0,0 +1,69 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create villager groups from genie units. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, GenieUnitTaskGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_villager_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create task groups and assign the relevant male and female group to a + villager group. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + units = full_data_set.genie_units + task_group_ids = set() + unit_ids = set() + + # Find task groups in the dataset + for unit in units.values(): + if unit.has_member("task_group"): + task_group_id = unit["task_group"].value + + else: + task_group_id = 0 + + if task_group_id == 0: + # no task group + continue + + if task_group_id in task_group_ids: + task_group = full_data_set.task_groups[task_group_id] + task_group.add_unit(unit) + + else: + if task_group_id == 1: + line_id = GenieUnitTaskGroup.male_line_id + + elif task_group_id == 2: + line_id = GenieUnitTaskGroup.female_line_id + + else: + raise ValueError( + f"Unknown task group ID {task_group_id} for unit {unit['id0'].value}" + ) + + task_group = GenieUnitTaskGroup(line_id, task_group_id, full_data_set) + task_group.add_unit(unit) + full_data_set.task_groups.update({task_group_id: task_group}) + + task_group_ids.add(task_group_id) + unit_ids.add(unit["id0"].value) + + # Create the villager task group + villager = GenieVillagerGroup(118, task_group_ids, full_data_set) + full_data_set.unit_lines.update({villager.get_id(): villager}) + full_data_set.villager_groups.update({villager.get_id(): villager}) + for unit_id in unit_ids: + full_data_set.unit_ref.update({unit_id: villager}) diff --git a/openage/convert/processor/conversion/aoc/main/link/CMakeLists.txt b/openage/convert/processor/conversion/aoc/main/link/CMakeLists.txt new file mode 100644 index 0000000000..a9118dd0e6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/CMakeLists.txt @@ -0,0 +1,11 @@ +add_py_modules( + __init__.py + building_upgrade.py + civ_unique.py + creatable.py + garrison.py + gather.py + repairable.py + researchable.py + trade_post.py +) diff --git a/openage/convert/processor/conversion/aoc/main/link/__init__.py b/openage/convert/processor/conversion/aoc/main/link/__init__.py new file mode 100644 index 0000000000..7598391f4e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link AoC data to the game entities created from lines. +""" diff --git a/openage/convert/processor/conversion/aoc/main/link/building_upgrade.py b/openage/convert/processor/conversion/aoc/main/link/building_upgrade.py new file mode 100644 index 0000000000..1f7ef98e06 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/building_upgrade.py @@ -0,0 +1,42 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link building upgrades to building lines. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_building_upgrades(full_data_set: GenieObjectContainer) -> None: + """ + Find building upgrades in the AgeUp techs and append them to the building lines. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + age_ups = full_data_set.age_upgrades + + # Order of age ups should be correct + for age_up in age_ups.values(): + for effect in age_up.effects.get_effects(): + type_id = effect.get_type() + + if type_id != 3: + continue + + upgrade_source_id = effect["attr_a"].value + upgrade_target_id = effect["attr_b"].value + + if upgrade_source_id not in full_data_set.building_lines.keys(): + continue + + upgraded_line = full_data_set.building_lines[upgrade_source_id] + upgrade_target = full_data_set.genie_units[upgrade_target_id] + + upgraded_line.add_unit(upgrade_target) + full_data_set.unit_ref.update({upgrade_target_id: upgraded_line}) diff --git a/openage/convert/processor/conversion/aoc/main/link/civ_unique.py b/openage/convert/processor/conversion/aoc/main/link/civ_unique.py new file mode 100644 index 0000000000..94d4223ab6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/civ_unique.py @@ -0,0 +1,49 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link unique lines, techs, etc. to civ groups. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_civ_uniques(full_data_set: GenieObjectContainer) -> None: + """ + Link civ bonus techs, unique units and unique techs to their civs. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + for bonus in full_data_set.civ_boni.values(): + civ_id = bonus.get_civilization() + full_data_set.civ_groups[civ_id].add_civ_bonus(bonus) + + for unit_line in full_data_set.unit_lines.values(): + if unit_line.is_unique(): + head_unit_id = unit_line.get_head_unit_id() + head_unit_connection = full_data_set.unit_connections[head_unit_id] + enabling_research_id = head_unit_connection["enabling_research"].value + enabling_research = full_data_set.genie_techs[enabling_research_id] + enabling_civ_id = enabling_research["civilization_id"].value + + full_data_set.civ_groups[enabling_civ_id].add_unique_entity(unit_line) + + for building_line in full_data_set.building_lines.values(): + if building_line.is_unique(): + head_unit_id = building_line.get_head_unit_id() + head_building_connection = full_data_set.building_connections[head_unit_id] + enabling_research_id = head_building_connection["enabling_research"].value + enabling_research = full_data_set.genie_techs[enabling_research_id] + enabling_civ_id = enabling_research["civilization_id"].value + + full_data_set.civ_groups[enabling_civ_id].add_unique_entity(building_line) + + for tech_group in full_data_set.tech_groups.values(): + if tech_group.is_unique() and tech_group.is_researchable(): + civ_id = tech_group.get_civilization() + full_data_set.civ_groups[civ_id].add_unique_tech(tech_group) diff --git a/openage/convert/processor/conversion/aoc/main/link/creatable.py b/openage/convert/processor/conversion/aoc/main/link/creatable.py new file mode 100644 index 0000000000..509475f783 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/creatable.py @@ -0,0 +1,43 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link creatable unit lines to building lines they can be created in. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_creatables(full_data_set: GenieObjectContainer) -> None: + """ + Link creatable units and buildings to their creating entity. This is done + to provide quick access during conversion. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + # Link units to buildings + unit_lines = full_data_set.unit_lines + + for unit_line in unit_lines.values(): + if unit_line.is_creatable(): + train_location_id = unit_line.get_train_location_id() + full_data_set.building_lines[train_location_id].add_creatable(unit_line) + + # Link buildings to villagers and fishing ships + building_lines = full_data_set.building_lines + + for building_line in building_lines.values(): + if building_line.is_creatable(): + train_location_id = building_line.get_train_location_id() + + if train_location_id in full_data_set.villager_groups.keys(): + full_data_set.villager_groups[train_location_id].add_creatable(building_line) + + else: + # try normal units + full_data_set.unit_lines[train_location_id].add_creatable(building_line) diff --git a/openage/convert/processor/conversion/aoc/main/link/garrison.py b/openage/convert/processor/conversion/aoc/main/link/garrison.py new file mode 100644 index 0000000000..43de5df878 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/garrison.py @@ -0,0 +1,125 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link garrisonable lines to their garrison locations and vice versa. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieGarrisonMode + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_garrison(full_data_set: GenieObjectContainer) -> None: + """ + Link a garrison unit to the lines that are stored and vice versa. This is done + to provide quick access during conversion. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + garrisoned_lines = {} + garrisoned_lines.update(full_data_set.unit_lines) + garrisoned_lines.update(full_data_set.ambient_groups) + + garrison_lines = {} + garrison_lines.update(full_data_set.unit_lines) + garrison_lines.update(full_data_set.building_lines) + + # Search through all units and look at their garrison commands + for unit_line in garrisoned_lines.values(): + garrison_classes = [] + garrison_units = [] + + if unit_line.has_command(3): + unit_commands = unit_line.get_head_unit()["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + if type_id != 3: + continue + + class_id = command["class_id"].value + if class_id > -1: + garrison_classes.append(class_id) + + if class_id == 3: + # Towers because Ensemble didn't like consistent rules + garrison_classes.append(52) + + unit_id = command["unit_id"].value + if unit_id > -1: + garrison_units.append(unit_id) + + for garrison_line in garrison_lines.values(): + if not garrison_line.is_garrison(): + continue + + # Natural garrison + garrison_mode = garrison_line.get_garrison_mode() + if garrison_mode == GenieGarrisonMode.NATURAL: + if unit_line.get_head_unit().has_member("creatable_type"): + creatable_type = unit_line.get_head_unit()["creatable_type"].value + + else: + creatable_type = 0 + + if garrison_line.get_head_unit().has_member("garrison_type"): + garrison_type = garrison_line.get_head_unit()["garrison_type"].value + + else: + garrison_type = 0 + + if creatable_type == 1 and not garrison_type & 0x01: + continue + + if creatable_type == 2 and not garrison_type & 0x02: + continue + + if creatable_type == 3 and not garrison_type & 0x04: + continue + + if creatable_type == 6 and not garrison_type & 0x08: + continue + + if garrison_line.get_class_id() in garrison_classes: + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) + continue + + if garrison_line.get_head_unit_id() in garrison_units: + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) + continue + + # Transports/ unit garrisons (no conditions) + elif garrison_mode in (GenieGarrisonMode.TRANSPORT, + GenieGarrisonMode.UNIT_GARRISON): + if garrison_line.get_class_id() in garrison_classes: + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) + + # Self produced units (these cannot be determined from commands) + elif garrison_mode == GenieGarrisonMode.SELF_PRODUCED: + if unit_line in garrison_line.creates: + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) + + # Monk inventories + elif garrison_mode == GenieGarrisonMode.MONK: + # Search for a pickup command + unit_commands = garrison_line.get_head_unit()["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + if type_id != 132: + continue + + unit_id = command["unit_id"].value + if unit_id == unit_line.get_head_unit_id(): + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) diff --git a/openage/convert/processor/conversion/aoc/main/link/gather.py b/openage/convert/processor/conversion/aoc/main/link/gather.py new file mode 100644 index 0000000000..c8755c0789 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/gather.py @@ -0,0 +1,35 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link gather objects to entity lines. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_gatherers_to_dropsites(full_data_set: GenieObjectContainer) -> None: + """ + Link gatherers to the buildings they drop resources off. This is done + to provide quick access during conversion. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + villager_groups = full_data_set.villager_groups + + for villager in villager_groups.values(): + for unit in villager.variants[0].line: + drop_site_members = unit["drop_sites"].value + unit_id = unit["id0"].value + + for drop_site_member in drop_site_members: + drop_site_id = drop_site_member.value + + if drop_site_id > -1: + drop_site = full_data_set.building_lines[drop_site_id] + drop_site.add_gatherer_id(unit_id) diff --git a/openage/convert/processor/conversion/aoc/main/link/repairable.py b/openage/convert/processor/conversion/aoc/main/link/repairable.py new file mode 100644 index 0000000000..600d0c6033 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/repairable.py @@ -0,0 +1,52 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link repairable units/buildings to villager groups. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_repairables(full_data_set: GenieObjectContainer) -> None: + """ + Set units/buildings as repairable + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + villager_groups = full_data_set.villager_groups + + repair_lines = {} + repair_lines.update(full_data_set.unit_lines) + repair_lines.update(full_data_set.building_lines) + + repair_classes = [] + for villager in villager_groups.values(): + repair_unit = villager.get_units_with_command(106)[0] + unit_commands = repair_unit["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + if type_id != 106: + continue + + class_id = command["class_id"].value + if class_id == -1: + # Buildings/Siege + repair_classes.append(3) + repair_classes.append(13) + repair_classes.append(52) + repair_classes.append(54) + repair_classes.append(55) + + else: + repair_classes.append(class_id) + + for repair_line in repair_lines.values(): + if repair_line.get_class_id() in repair_classes: + repair_line.repairable = True diff --git a/openage/convert/processor/conversion/aoc/main/link/researchable.py b/openage/convert/processor/conversion/aoc/main/link/researchable.py new file mode 100644 index 0000000000..be446bf00d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/researchable.py @@ -0,0 +1,28 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link researchable techs to building lines they can be researched in. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_researchables(full_data_set: GenieObjectContainer) -> None: + """ + Link techs to their buildings. This is done + to provide quick access during conversion. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + tech_groups = full_data_set.tech_groups + + for tech in tech_groups.values(): + if tech.is_researchable(): + research_location_id = tech.get_research_location_id() + full_data_set.building_lines[research_location_id].add_researchable(tech) diff --git a/openage/convert/processor/conversion/aoc/main/link/trade_post.py b/openage/convert/processor/conversion/aoc/main/link/trade_post.py new file mode 100644 index 0000000000..3eda4143ee --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/trade_post.py @@ -0,0 +1,40 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link trade posts to the building lines that they trade with. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_trade_posts(full_data_set: GenieObjectContainer) -> None: + """ + Link a trade post building to the lines that it trades with. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + """ + unit_lines = full_data_set.unit_lines.values() + + for unit_line in unit_lines: + if unit_line.has_command(111): + head_unit = unit_line.get_head_unit() + unit_commands = head_unit["unit_commands"].value + trade_post_id = -1 + for command in unit_commands: + # Find the trade command and the trade post id + type_id = command["type"].value + + if type_id != 111: + continue + + trade_post_id = command["unit_id"].value + + # Notify buiding + if trade_post_id in full_data_set.building_lines.keys(): + full_data_set.building_lines[trade_post_id].add_trading_line(unit_line) diff --git a/openage/convert/processor/conversion/aoc/processor.py b/openage/convert/processor/conversion/aoc/processor.py index d042e32065..2c5d1e5e4a 100644 --- a/openage/convert/processor/conversion/aoc/processor.py +++ b/openage/convert/processor/conversion/aoc/processor.py @@ -1,58 +1,55 @@ -# Copyright 2019-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-lines,too-many-branches,too-many-statements -# pylint: disable=too-many-locals,too-many-public-methods +# Copyright 2019-2025 the openage authors. See copying.md for legal info. + """ Convert data from AoC to openage formats. """ from __future__ import annotations import typing - from .....log import info -from ....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup -from ....entity_object.conversion.aoc.genie_civ import GenieCivilizationObject -from ....entity_object.conversion.aoc.genie_connection import GenieAgeConnection, \ - GenieBuildingConnection, GenieUnitConnection, GenieTechConnection -from ....entity_object.conversion.aoc.genie_effect import GenieEffectObject, \ - GenieEffectBundle -from ....entity_object.conversion.aoc.genie_graphic import GenieGraphic from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer -from ....entity_object.conversion.aoc.genie_sound import GenieSound -from ....entity_object.conversion.aoc.genie_tech import AgeUpgrade, \ - UnitUnlock, UnitLineUpgrade, CivBonus -from ....entity_object.conversion.aoc.genie_tech import BuildingLineUpgrade -from ....entity_object.conversion.aoc.genie_tech import GenieTechObject -from ....entity_object.conversion.aoc.genie_tech import StatUpgrade, InitiatedTech, \ - BuildingUnlock -from ....entity_object.conversion.aoc.genie_terrain import GenieTerrainGroup, \ - GenieTerrainObject, GenieTerrainRestriction -from ....entity_object.conversion.aoc.genie_unit import GenieAmbientGroup, \ - GenieGarrisonMode -from ....entity_object.conversion.aoc.genie_unit import GenieStackBuildingGroup, \ - GenieBuildingLineGroup -from ....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup, \ - GenieUnitTransformGroup, GenieMonkGroup -from ....entity_object.conversion.aoc.genie_unit import GenieUnitObject -from ....entity_object.conversion.aoc.genie_unit import GenieUnitTaskGroup, \ - GenieVillagerGroup -from ....entity_object.conversion.aoc.genie_unit import GenieVariantGroup from ....service.debug_info import debug_converter_objects, \ debug_converter_object_groups from ....service.read.nyan_api_loader import load_api -from ....value_object.conversion.aoc.internal_nyan_names import AMBIENT_GROUP_LOOKUPS, \ - VARIANT_GROUP_LOOKUPS from .media_subprocessor import AoCMediaSubprocessor from .modpack_subprocessor import AoCModpackSubprocessor from .nyan_subprocessor import AoCNyanSubprocessor from .pregen_processor import AoCPregenSubprocessor +from .main.extract.civ import extract_genie_civs +from .main.extract.connection import extract_age_connections, extract_building_connections, \ + extract_unit_connections, extract_tech_connections +from .main.extract.effect_bundle import extract_genie_effect_bundles, sanitize_effect_bundles +from .main.extract.graphics import extract_genie_graphics +from .main.extract.sound import extract_genie_sounds +from .main.extract.tech import extract_genie_techs +from .main.extract.terrain import extract_genie_terrains, extract_genie_restrictions +from .main.extract.unit import extract_genie_units + +from .main.groups.ambient_group import create_ambient_groups +from .main.groups.civ_group import create_civ_groups +from .main.groups.building_line import create_building_lines +from .main.groups.tech_group import create_tech_groups +from .main.groups.terrain_group import create_terrain_groups +from .main.groups.unit_line import create_unit_lines, create_extra_unit_lines +from .main.groups.variant_group import create_variant_groups +from .main.groups.villager_group import create_villager_groups + +from .main.link.building_upgrade import link_building_upgrades +from .main.link.creatable import link_creatables +from .main.link.civ_unique import link_civ_uniques +from .main.link.gather import link_gatherers_to_dropsites +from .main.link.garrison import link_garrison +from .main.link.repairable import link_repairables +from .main.link.researchable import link_researchables +from .main.link.trade_post import link_trade_posts + if typing.TYPE_CHECKING: from argparse import Namespace - from openage.convert.entity_object.conversion.stringresource import StringResource - from openage.convert.entity_object.conversion.modpack import Modpack - from openage.convert.value_object.read.value_members import ArrayMember - from openage.convert.value_object.init.game_version import GameVersion + from ....entity_object.conversion.stringresource import StringResource + from ....entity_object.conversion.modpack import Modpack + from ....value_object.read.value_members import ArrayMember + from ....value_object.init.game_version import GameVersion class AoCProcessor: @@ -122,22 +119,36 @@ def _pre_processor( info("Extracting Genie data...") - cls.extract_genie_units(gamespec, dataset) - cls.extract_genie_techs(gamespec, dataset) - cls.extract_genie_effect_bundles(gamespec, dataset) - cls.sanitize_effect_bundles(dataset) - cls.extract_genie_civs(gamespec, dataset) - cls.extract_age_connections(gamespec, dataset) - cls.extract_building_connections(gamespec, dataset) - cls.extract_unit_connections(gamespec, dataset) - cls.extract_tech_connections(gamespec, dataset) - cls.extract_genie_graphics(gamespec, dataset) - cls.extract_genie_sounds(gamespec, dataset) - cls.extract_genie_terrains(gamespec, dataset) - cls.extract_genie_restrictions(gamespec, dataset) + extract_genie_units(gamespec, dataset) + extract_genie_techs(gamespec, dataset) + extract_genie_effect_bundles(gamespec, dataset) + sanitize_effect_bundles(dataset) + extract_genie_civs(gamespec, dataset) + extract_age_connections(gamespec, dataset) + extract_building_connections(gamespec, dataset) + extract_unit_connections(gamespec, dataset) + extract_tech_connections(gamespec, dataset) + extract_genie_graphics(gamespec, dataset) + extract_genie_sounds(gamespec, dataset) + extract_genie_terrains(gamespec, dataset) + extract_genie_restrictions(gamespec, dataset) return dataset + extract_genie_units = staticmethod(extract_genie_units) + extract_genie_techs = staticmethod(extract_genie_techs) + extract_genie_effect_bundles = staticmethod(extract_genie_effect_bundles) + sanitize_effect_bundles = staticmethod(sanitize_effect_bundles) + extract_genie_civs = staticmethod(extract_genie_civs) + extract_age_connections = staticmethod(extract_age_connections) + extract_building_connections = staticmethod(extract_building_connections) + extract_unit_connections = staticmethod(extract_unit_connections) + extract_tech_connections = staticmethod(extract_tech_connections) + extract_genie_graphics = staticmethod(extract_genie_graphics) + extract_genie_sounds = staticmethod(extract_genie_sounds) + extract_genie_terrains = staticmethod(extract_genie_terrains) + extract_genie_restrictions = staticmethod(extract_genie_restrictions) + @classmethod def _processor(cls, full_data_set: GenieObjectContainer) -> GenieObjectContainer: """ @@ -152,26 +163,26 @@ def _processor(cls, full_data_set: GenieObjectContainer) -> GenieObjectContainer info("Creating API-like objects...") - cls.create_unit_lines(full_data_set) - cls.create_extra_unit_lines(full_data_set) - cls.create_building_lines(full_data_set) - cls.create_villager_groups(full_data_set) - cls.create_ambient_groups(full_data_set) - cls.create_variant_groups(full_data_set) - cls.create_terrain_groups(full_data_set) - cls.create_tech_groups(full_data_set) - cls.create_civ_groups(full_data_set) + create_unit_lines(full_data_set) + create_extra_unit_lines(full_data_set) + create_building_lines(full_data_set) + create_villager_groups(full_data_set) + create_ambient_groups(full_data_set) + create_variant_groups(full_data_set) + create_terrain_groups(full_data_set) + create_tech_groups(full_data_set) + create_civ_groups(full_data_set) info("Linking API-like objects...") - cls.link_building_upgrades(full_data_set) - cls.link_creatables(full_data_set) - cls.link_researchables(full_data_set) - cls.link_civ_uniques(full_data_set) - cls.link_gatherers_to_dropsites(full_data_set) - cls.link_garrison(full_data_set) - cls.link_trade_posts(full_data_set) - cls.link_repairables(full_data_set) + link_building_upgrades(full_data_set) + link_creatables(full_data_set) + link_researchables(full_data_set) + link_civ_uniques(full_data_set) + link_gatherers_to_dropsites(full_data_set) + link_garrison(full_data_set) + link_trade_posts(full_data_set) + link_repairables(full_data_set) info("Generating auxiliary objects...") @@ -179,6 +190,25 @@ def _processor(cls, full_data_set: GenieObjectContainer) -> GenieObjectContainer return full_data_set + create_unit_lines = staticmethod(create_unit_lines) + create_extra_unit_lines = staticmethod(create_extra_unit_lines) + create_building_lines = staticmethod(create_building_lines) + create_tech_groups = staticmethod(create_tech_groups) + create_civ_groups = staticmethod(create_civ_groups) + create_villager_groups = staticmethod(create_villager_groups) + create_ambient_groups = staticmethod(create_ambient_groups) + create_variant_groups = staticmethod(create_variant_groups) + create_terrain_groups = staticmethod(create_terrain_groups) + + link_building_upgrades = staticmethod(link_building_upgrades) + link_creatables = staticmethod(link_creatables) + link_civ_uniques = staticmethod(link_civ_uniques) + link_researchables = staticmethod(link_researchables) + link_gatherers_to_dropsites = staticmethod(link_gatherers_to_dropsites) + link_garrison = staticmethod(link_garrison) + link_trade_posts = staticmethod(link_trade_posts) + link_repairables = staticmethod(link_repairables) + @classmethod def _post_processor(cls, full_data_set: GenieObjectContainer) -> list[Modpack]: """ @@ -199,1211 +229,3 @@ def _post_processor(cls, full_data_set: GenieObjectContainer) -> list[Modpack]: AoCMediaSubprocessor.convert(full_data_set) return AoCModpackSubprocessor.get_modpacks(full_data_set) - - @staticmethod - def extract_genie_units(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract units from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: ...dataformat.value_members.ArrayMember - """ - # Units are stored in the civ container. - # All civs point to the same units (?) except for Gaia which has more. - # Gaia also seems to have the most units, so we only read from Gaia - # - # call hierarchy: wrapper[0]->civs[0]->units - raw_units = gamespec[0]["civs"][0]["units"].value - - # Unit headers store the things units can do - raw_unit_headers = gamespec[0]["unit_headers"].value - - for raw_unit in raw_units: - unit_id = raw_unit["id0"].value - unit_members = raw_unit.value - - # Turn attack and armor into containers to make diffing work - if "attacks" in unit_members.keys(): - attacks_member = unit_members.pop("attacks") - attacks_member = attacks_member.get_container("type_id") - armors_member = unit_members.pop("armors") - armors_member = armors_member.get_container("type_id") - - unit_members.update({"attacks": attacks_member}) - unit_members.update({"armors": armors_member}) - - unit = GenieUnitObject(unit_id, full_data_set, members=unit_members) - full_data_set.genie_units.update({unit.get_id(): unit}) - - # Commands - unit_commands = raw_unit_headers[unit_id]["unit_commands"] - unit.add_member(unit_commands) - - @staticmethod - def extract_genie_techs(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract techs from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: ...dataformat.value_members.ArrayMember - """ - # Techs are stored as "researches". - # - # call hierarchy: wrapper[0]->researches - raw_techs = gamespec[0]["researches"].value - - index = 0 - for raw_tech in raw_techs: - tech_id = index - tech_members = raw_tech.value - - tech = GenieTechObject(tech_id, full_data_set, members=tech_members) - full_data_set.genie_techs.update({tech.get_id(): tech}) - - index += 1 - - @staticmethod - def extract_genie_effect_bundles( - gamespec: ArrayMember, - full_data_set: GenieObjectContainer - ) -> None: - """ - Extract effects and effect bundles from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->effect_bundles - raw_effect_bundles = gamespec[0]["effect_bundles"].value - - index_bundle = 0 - for raw_effect_bundle in raw_effect_bundles: - bundle_id = index_bundle - - # call hierarchy: effect_bundle->effects - raw_effects = raw_effect_bundle["effects"].value - - effects = {} - - index_effect = 0 - for raw_effect in raw_effects: - effect_id = index_effect - effect_members = raw_effect.value - - effect = GenieEffectObject(effect_id, bundle_id, full_data_set, - members=effect_members) - - effects.update({effect_id: effect}) - - index_effect += 1 - - # Pass everything to the bundle - effect_bundle_members = raw_effect_bundle.value - # Remove effects we store them as separate objects - effect_bundle_members.pop("effects") - - bundle = GenieEffectBundle(bundle_id, effects, full_data_set, - members=effect_bundle_members) - full_data_set.genie_effect_bundles.update({bundle.get_id(): bundle}) - - index_bundle += 1 - - @staticmethod - def extract_genie_civs( - gamespec: ArrayMember, - full_data_set: GenieObjectContainer - ) -> None: - """ - Extract civs from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->civs - raw_civs = gamespec[0]["civs"].value - - index = 0 - for raw_civ in raw_civs: - civ_id = index - - civ_members = raw_civ.value - units_member = civ_members.pop("units") - units_member = units_member.get_container("id0") - - civ_members.update({"units": units_member}) - - civ = GenieCivilizationObject(civ_id, full_data_set, members=civ_members) - full_data_set.genie_civs.update({civ.get_id(): civ}) - - index += 1 - - @staticmethod - def extract_age_connections(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract age connections from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->age_connections - raw_connections = gamespec[0]["age_connections"].value - - for raw_connection in raw_connections: - age_id = raw_connection["id"].value - connection_members = raw_connection.value - - connection = GenieAgeConnection(age_id, full_data_set, members=connection_members) - full_data_set.age_connections.update({connection.get_id(): connection}) - - @staticmethod - def extract_building_connections( - gamespec: ArrayMember, - full_data_set: GenieObjectContainer - ) -> None: - """ - Extract building connections from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->building_connections - raw_connections = gamespec[0]["building_connections"].value - - for raw_connection in raw_connections: - building_id = raw_connection["id"].value - connection_members = raw_connection.value - - connection = GenieBuildingConnection(building_id, full_data_set, - members=connection_members) - full_data_set.building_connections.update({connection.get_id(): connection}) - - @staticmethod - def extract_unit_connections( - gamespec: ArrayMember, - full_data_set: GenieObjectContainer - ) -> None: - """ - Extract unit connections from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->unit_connections - raw_connections = gamespec[0]["unit_connections"].value - - for raw_connection in raw_connections: - unit_id = raw_connection["id"].value - connection_members = raw_connection.value - - connection = GenieUnitConnection(unit_id, full_data_set, members=connection_members) - full_data_set.unit_connections.update({connection.get_id(): connection}) - - @staticmethod - def extract_tech_connections( - gamespec: ArrayMember, - full_data_set: GenieObjectContainer - ) -> None: - """ - Extract tech connections from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->tech_connections - raw_connections = gamespec[0]["tech_connections"].value - - for raw_connection in raw_connections: - tech_id = raw_connection["id"].value - connection_members = raw_connection.value - - connection = GenieTechConnection(tech_id, full_data_set, members=connection_members) - full_data_set.tech_connections.update({connection.get_id(): connection}) - - @staticmethod - def extract_genie_graphics(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract graphic definitions from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->graphics - raw_graphics = gamespec[0]["graphics"].value - - for raw_graphic in raw_graphics: - # Can be ignored if there is no filename associated - filename = raw_graphic["filename"].value - if not filename: - continue - - graphic_id = raw_graphic["graphic_id"].value - graphic_members = raw_graphic.value - - graphic = GenieGraphic(graphic_id, full_data_set, members=graphic_members) - slp_id = raw_graphic["slp_id"].value - if str(slp_id) not in full_data_set.existing_graphics: - graphic.exists = False - - full_data_set.genie_graphics.update({graphic.get_id(): graphic}) - - # Detect subgraphics - for genie_graphic in full_data_set.genie_graphics.values(): - genie_graphic.detect_subgraphics() - - @staticmethod - def extract_genie_sounds(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract sound definitions from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->sounds - raw_sounds = gamespec[0]["sounds"].value - - for raw_sound in raw_sounds: - sound_id = raw_sound["sound_id"].value - sound_members = raw_sound.value - - sound = GenieSound(sound_id, full_data_set, members=sound_members) - full_data_set.genie_sounds.update({sound.get_id(): sound}) - - @staticmethod - def extract_genie_terrains(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract terrains from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->terrains - raw_terrains = gamespec[0]["terrains"].value - - for index, raw_terrain in enumerate(raw_terrains): - terrain_index = index - terrain_members = raw_terrain.value - - terrain = GenieTerrainObject(terrain_index, full_data_set, members=terrain_members) - full_data_set.genie_terrains.update({terrain.get_id(): terrain}) - - @staticmethod - def extract_genie_restrictions( - gamespec: ArrayMember, - full_data_set: GenieObjectContainer - ) -> None: - """ - Extract terrain restrictions from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->terrains - raw_restrictions = gamespec[0]["terrain_restrictions"].value - - for index, raw_restriction in enumerate(raw_restrictions): - restriction_index = index - restriction_members = raw_restriction.value - - restriction = GenieTerrainRestriction(restriction_index, - full_data_set, - members=restriction_members) - full_data_set.genie_terrain_restrictions.update({restriction.get_id(): restriction}) - - @staticmethod - def create_unit_lines(full_data_set: GenieObjectContainer) -> None: - """ - Sort units into lines, based on information in the unit connections. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - unit_connections = full_data_set.unit_connections - - # First only handle the line heads (firstunits in a line) - for connection in unit_connections.values(): - unit_id = connection["id"].value - unit = full_data_set.genie_units[unit_id] - line_mode = connection["line_mode"].value - - if line_mode != 2: - # It's an upgrade. Skip and handle later - continue - - # Check for special cases first - if unit.has_member("transform_unit_id")\ - and unit["transform_unit_id"].value > -1: - # Trebuchet - unit_line = GenieUnitTransformGroup(unit_id, unit_id, full_data_set) - full_data_set.transform_groups.update({unit_line.get_id(): unit_line}) - - elif unit_id == 125: - # Monks - # Switch to monk with relic is hardcoded :( - unit_line = GenieMonkGroup(unit_id, unit_id, 286, full_data_set) - full_data_set.monk_groups.update({unit_line.get_id(): unit_line}) - - elif unit.has_member("task_group")\ - and unit["task_group"].value > 0: - # Villager - # done somewhere else because they are special^TM - continue - - else: - # Normal units - unit_line = GenieUnitLineGroup(unit_id, full_data_set) - - unit_line.add_unit(unit) - full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) - full_data_set.unit_ref.update({unit_id: unit_line}) - - # Second, handle all upgraded units - for connection in unit_connections.values(): - unit_id = connection["id"].value - unit = full_data_set.genie_units[unit_id] - line_mode = connection["line_mode"].value - - if line_mode != 3: - # This unit is not an upgrade and was handled in the last for-loop - continue - - # Search other_connections for the previous unit in line - connected_types = connection["other_connections"].value - for index, _ in enumerate(connected_types): - connected_type = connected_types[index]["other_connection"].value - if connected_type == 2: - # 2 == Unit - connected_index = index - break - - else: - raise RuntimeError(f"Unit {unit_id} is not first in line, but no previous " - "unit can be found in other_connections") - - connected_ids = connection["other_connected_ids"].value - previous_unit_id = connected_ids[connected_index].value - - # Search for the first unit ID in the line recursively - previous_id = previous_unit_id - previous_connection = unit_connections[previous_unit_id] - while previous_connection["line_mode"] != 2: - if previous_id in full_data_set.unit_ref.keys(): - # Short-circuit here, if we the previous unit was already handled - break - - connected_types = previous_connection["other_connections"].value - connected_index = -1 - for index, _ in enumerate(connected_types): - connected_type = connected_types[index]["other_connection"].value - if connected_type == 2: - # 2 == Unit - connected_index = index - break - - connected_ids = previous_connection["other_connected_ids"].value - previous_id = connected_ids[connected_index].value - previous_connection = unit_connections[previous_id] - - unit_line = full_data_set.unit_ref[previous_id] - unit_line.add_unit(unit, after=previous_unit_id) - full_data_set.unit_ref.update({unit_id: unit_line}) - - @staticmethod - def create_extra_unit_lines(full_data_set: GenieObjectContainer) -> None: - """ - Create additional units that are not in the unit connections. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - extra_units = (48, 65, 594, 833) # Wildlife - - for unit_id in extra_units: - unit_line = GenieUnitLineGroup(unit_id, full_data_set) - unit_line.add_unit(full_data_set.genie_units[unit_id]) - full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) - full_data_set.unit_ref.update({unit_id: unit_line}) - - @staticmethod - def create_building_lines(full_data_set: GenieObjectContainer) -> None: - """ - Establish building lines, based on information in the building connections. - Because of how Genie building lines work, this will only find the first - building in the line. Subsequent buildings in the line have to be determined - by effects in AgeUpTechs. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - building_connections = full_data_set.building_connections - - for connection in building_connections.values(): - building_id = connection["id"].value - building = full_data_set.genie_units[building_id] - previous_building_id = None - stack_building = False - - # Buildings have no actual lines, so we use - # their unit ID as the line ID. - line_id = building_id - - # Check if we have to create a GenieStackBuildingGroup - if building.has_member("stack_unit_id") and \ - building["stack_unit_id"].value > -1: - stack_building = True - - if building.has_member("head_unit_id") and \ - building["head_unit_id"].value > -1: - # we don't care about head units because we process - # them with their stack unit - continue - - # Check if the building is part of an existing line. - # To do this, we look for connected techs and - # check if any tech has an upgrade effect. - connected_types = connection["other_connections"].value - connected_tech_indices = [] - for index, _ in enumerate(connected_types): - connected_type = connected_types[index]["other_connection"].value - if connected_type == 3: - # 3 == Tech - connected_tech_indices.append(index) - - connected_ids = connection["other_connected_ids"].value - - for index in connected_tech_indices: - connected_tech_id = connected_ids[index].value - connected_tech = full_data_set.genie_techs[connected_tech_id] - effect_bundle_id = connected_tech["tech_effect_id"].value - effect_bundle = full_data_set.genie_effect_bundles[effect_bundle_id] - - upgrade_effects = effect_bundle.get_effects(effect_type=3) - - if len(upgrade_effects) == 0: - continue - - # Search upgrade effects for the line_id - for upgrade in upgrade_effects: - upgrade_source = upgrade["attr_a"].value - upgrade_target = upgrade["attr_b"].value - - # Check if the upgrade target is correct - if upgrade_target == building_id: - # Line id is the source building id - line_id = upgrade_source - break - - else: - # If no upgrade was found, then search remaining techs - continue - - # Find the previous building - for c_index, _ in enumerate(connected_types): - connected_type = connected_types[c_index]["other_connection"].value - if connected_type == 1: - # 1 == Building - connected_index = c_index - break - - else: - raise RuntimeError(f"Building {building_id} is not first in line, but no " - "previous building could be found in other_connections") - - previous_building_id = connected_ids[connected_index].value - break - - if line_id == building_id: - # First building in line - if stack_building: - stack_unit_id = building["stack_unit_id"].value - building_line = GenieStackBuildingGroup(stack_unit_id, line_id, full_data_set) - - else: - building_line = GenieBuildingLineGroup(line_id, full_data_set) - - full_data_set.building_lines.update({building_line.get_id(): building_line}) - building_line.add_unit(building, after=previous_building_id) - full_data_set.unit_ref.update({building_id: building_line}) - - else: - # It's an upgraded building - building_line = full_data_set.building_lines[line_id] - building_line.add_unit(building, after=previous_building_id) - full_data_set.unit_ref.update({building_id: building_line}) - - @staticmethod - def sanitize_effect_bundles(full_data_set: GenieObjectContainer) -> None: - """ - Remove garbage data from effect bundles. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - effect_bundles = full_data_set.genie_effect_bundles - - for bundle in effect_bundles.values(): - sanitized_effects = {} - - effects = bundle.get_effects() - - index = 0 - for effect in effects: - effect_type = effect["type_id"].value - if effect_type < 0: - # Effect has no type - continue - - if effect_type == 3: - if effect["attr_b"].value < 0: - # Upgrade to invalid unit - continue - - if effect_type == 102: - if effect["attr_d"].value < 0: - # Tech disable effect with no tech id specified - continue - - sanitized_effects.update({index: effect}) - index += 1 - - bundle.effects = sanitized_effects - bundle.sanitized = True - - @staticmethod - def create_tech_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create techs from tech connections and unit upgrades/unlocks - from unit connections. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - tech_connections = full_data_set.tech_connections - - # In tech connection are age ups, building unlocks/upgrades and stat upgrades - for connection in tech_connections.values(): - connected_buildings = connection["buildings"].value - tech_id = connection["id"].value - tech = full_data_set.genie_techs[tech_id] - - effect_id = tech["tech_effect_id"].value - if effect_id < 0: - continue - - tech_effects = full_data_set.genie_effect_bundles[effect_id] - - # Check if the tech is an age upgrade - tech_found = False - resource_effects = tech_effects.get_effects(effect_type=1) - for effect in resource_effects: - # Resource ID 6: Current Age - if effect["attr_a"].value != 6: - continue - - age_id = effect["attr_b"].value - age_up = AgeUpgrade(tech_id, age_id, full_data_set) - full_data_set.tech_groups.update({age_up.get_id(): age_up}) - full_data_set.age_upgrades.update({age_up.get_id(): age_up}) - tech_found = True - break - - if tech_found: - continue - - if len(connected_buildings) > 0: - # Building unlock or upgrade - unlock_effects = tech_effects.get_effects(effect_type=2) - upgrade_effects = tech_effects.get_effects(effect_type=2) - if len(unlock_effects) > 0: - unlock = unlock_effects[0] - unlock_id = unlock["attr_a"].value - - building_unlock = BuildingUnlock(tech_id, unlock_id, full_data_set) - full_data_set.tech_groups.update( - {building_unlock.get_id(): building_unlock} - ) - full_data_set.building_unlocks.update( - {building_unlock.get_id(): building_unlock} - ) - continue - - if len(upgrade_effects) > 0: - upgrade = upgrade_effects[0] - line_id = upgrade["attr_a"].value - upgrade_id = upgrade["attr_b"].value - - building_upgrade = BuildingLineUpgrade( - tech_id, - line_id, - upgrade_id, - full_data_set - ) - full_data_set.tech_groups.update( - {building_upgrade.get_id(): building_upgrade} - ) - full_data_set.building_upgrades.update( - {building_upgrade.get_id(): building_upgrade} - ) - continue - - # Create a stat upgrade for other techs - stat_up = StatUpgrade(tech_id, full_data_set) - full_data_set.tech_groups.update({stat_up.get_id(): stat_up}) - full_data_set.stat_upgrades.update({stat_up.get_id(): stat_up}) - - # Unit upgrades and unlocks are stored in unit connections - unit_connections = full_data_set.unit_connections - for connection in unit_connections.values(): - unit_id = connection["id"].value - required_research_id = connection["required_research"].value - enabling_research_id = connection["enabling_research"].value - line_mode = connection["line_mode"].value - line_id = full_data_set.unit_ref[unit_id].get_id() - - if required_research_id == -1 and enabling_research_id == -1: - # Unit is unlocked from the start - continue - - if line_mode == 2: - # Unit is first in line, there should be an unlock tech id - # This is usually the enabling tech id - unlock_tech_id = enabling_research_id - if unlock_tech_id == -1: - # Battle elephant is a curious exception wher it's the required tech id - unlock_tech_id = required_research_id - - unit_unlock = UnitUnlock(unlock_tech_id, line_id, full_data_set) - full_data_set.tech_groups.update({unit_unlock.get_id(): unit_unlock}) - full_data_set.unit_unlocks.update({unit_unlock.get_id(): unit_unlock}) - - elif line_mode == 3: - # Units further down the line receive line upgrades - unit_upgrade = UnitLineUpgrade(required_research_id, line_id, - unit_id, full_data_set) - full_data_set.tech_groups.update({unit_upgrade.get_id(): unit_upgrade}) - full_data_set.unit_upgrades.update({unit_upgrade.get_id(): unit_upgrade}) - - # Initiated techs are stored with buildings - genie_units = full_data_set.genie_units - - for genie_unit in genie_units.values(): - if not genie_unit.has_member("research_id"): - continue - - building_id = genie_unit["id0"].value - initiated_tech_id = genie_unit["research_id"].value - - if initiated_tech_id == -1: - continue - - if building_id not in full_data_set.building_lines.keys(): - # Skips upgraded buildings (which initiate the same techs) - continue - - initiated_tech = InitiatedTech(initiated_tech_id, building_id, full_data_set) - full_data_set.tech_groups.update({initiated_tech.get_id(): initiated_tech}) - full_data_set.initiated_techs.update({initiated_tech.get_id(): initiated_tech}) - - # Civ boni have to be aquired from techs - # Civ boni = ONLY passive boni (not unit unlocks, unit upgrades or team bonus) - genie_techs = full_data_set.genie_techs - - for index, _ in enumerate(genie_techs): - tech_id = index - - # Civ ID must be positive and non-zero - civ_id = genie_techs[index]["civilization_id"].value - if civ_id <= 0: - continue - - # Passive boni are not researched anywhere - research_location_id = genie_techs[index]["research_location_id"].value - if research_location_id > 0: - continue - - # Passive boni are not available in full tech mode - full_tech_mode = genie_techs[index]["full_tech_mode"].value - if full_tech_mode: - continue - - civ_bonus = CivBonus(tech_id, civ_id, full_data_set) - full_data_set.tech_groups.update({civ_bonus.get_id(): civ_bonus}) - full_data_set.civ_boni.update({civ_bonus.get_id(): civ_bonus}) - - @staticmethod - def create_civ_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create civilization groups from civ objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - civ_objects = full_data_set.genie_civs - - for index in range(len(civ_objects)): - civ_id = index - - civ_group = GenieCivilizationGroup(civ_id, full_data_set) - full_data_set.civ_groups.update({civ_group.get_id(): civ_group}) - - index += 1 - - @staticmethod - def create_villager_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create task groups and assign the relevant male and female group to a - villager group. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - units = full_data_set.genie_units - task_group_ids = set() - unit_ids = set() - - # Find task groups in the dataset - for unit in units.values(): - if unit.has_member("task_group"): - task_group_id = unit["task_group"].value - - else: - task_group_id = 0 - - if task_group_id == 0: - # no task group - continue - - if task_group_id in task_group_ids: - task_group = full_data_set.task_groups[task_group_id] - task_group.add_unit(unit) - - else: - if task_group_id == 1: - line_id = GenieUnitTaskGroup.male_line_id - - elif task_group_id == 2: - line_id = GenieUnitTaskGroup.female_line_id - - task_group = GenieUnitTaskGroup(line_id, task_group_id, full_data_set) - task_group.add_unit(unit) - full_data_set.task_groups.update({task_group_id: task_group}) - - task_group_ids.add(task_group_id) - unit_ids.add(unit["id0"].value) - - # Create the villager task group - villager = GenieVillagerGroup(118, task_group_ids, full_data_set) - full_data_set.unit_lines.update({villager.get_id(): villager}) - full_data_set.villager_groups.update({villager.get_id(): villager}) - for unit_id in unit_ids: - full_data_set.unit_ref.update({unit_id: villager}) - - @staticmethod - def create_ambient_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create ambient groups, mostly for resources and scenery. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - ambient_ids = AMBIENT_GROUP_LOOKUPS.keys() - genie_units = full_data_set.genie_units - - for ambient_id in ambient_ids: - ambient_group = GenieAmbientGroup(ambient_id, full_data_set) - ambient_group.add_unit(genie_units[ambient_id]) - full_data_set.ambient_groups.update({ambient_group.get_id(): ambient_group}) - full_data_set.unit_ref.update({ambient_id: ambient_group}) - - @staticmethod - def create_variant_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create variant groups. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - variants = VARIANT_GROUP_LOOKUPS - - for group_id, variant in variants.items(): - variant_group = GenieVariantGroup(group_id, full_data_set) - full_data_set.variant_groups.update({variant_group.get_id(): variant_group}) - - for variant_id in variant[2]: - variant_group.add_unit(full_data_set.genie_units[variant_id]) - full_data_set.unit_ref.update({variant_id: variant_group}) - - @staticmethod - def create_terrain_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create terrain groups. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - terrains = full_data_set.genie_terrains.values() - - for terrain in terrains: - slp_id = terrain["slp_id"].value - replacement_id = terrain["terrain_replacement_id"].value - - if slp_id == -1 and replacement_id == -1: - # No graphics and no graphics replacement means this terrain is unused - continue - - enabled = terrain["enabled"].value - - if enabled: - terrain_group = GenieTerrainGroup(terrain.get_id(), full_data_set) - full_data_set.terrain_groups.update({terrain.get_id(): terrain_group}) - - @staticmethod - def link_building_upgrades(full_data_set: GenieObjectContainer) -> None: - """ - Find building upgrades in the AgeUp techs and append them to the building lines. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - age_ups = full_data_set.age_upgrades - - # Order of age ups should be correct - for age_up in age_ups.values(): - for effect in age_up.effects.get_effects(): - type_id = effect.get_type() - - if type_id != 3: - continue - - upgrade_source_id = effect["attr_a"].value - upgrade_target_id = effect["attr_b"].value - - if upgrade_source_id not in full_data_set.building_lines.keys(): - continue - - upgraded_line = full_data_set.building_lines[upgrade_source_id] - upgrade_target = full_data_set.genie_units[upgrade_target_id] - - upgraded_line.add_unit(upgrade_target) - full_data_set.unit_ref.update({upgrade_target_id: upgraded_line}) - - @staticmethod - def link_creatables(full_data_set: GenieObjectContainer) -> None: - """ - Link creatable units and buildings to their creating entity. This is done - to provide quick access during conversion. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - # Link units to buildings - unit_lines = full_data_set.unit_lines - - for unit_line in unit_lines.values(): - if unit_line.is_creatable(): - train_location_id = unit_line.get_train_location_id() - full_data_set.building_lines[train_location_id].add_creatable(unit_line) - - # Link buildings to villagers and fishing ships - building_lines = full_data_set.building_lines - - for building_line in building_lines.values(): - if building_line.is_creatable(): - train_location_id = building_line.get_train_location_id() - - if train_location_id in full_data_set.villager_groups.keys(): - full_data_set.villager_groups[train_location_id].add_creatable(building_line) - - else: - # try normal units - full_data_set.unit_lines[train_location_id].add_creatable(building_line) - - @staticmethod - def link_researchables(full_data_set: GenieObjectContainer) -> None: - """ - Link techs to their buildings. This is done - to provide quick access during conversion. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - tech_groups = full_data_set.tech_groups - - for tech in tech_groups.values(): - if tech.is_researchable(): - research_location_id = tech.get_research_location_id() - full_data_set.building_lines[research_location_id].add_researchable(tech) - - @staticmethod - def link_civ_uniques(full_data_set: GenieObjectContainer) -> None: - """ - Link civ bonus techs, unique units and unique techs to their civs. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - for bonus in full_data_set.civ_boni.values(): - civ_id = bonus.get_civilization() - full_data_set.civ_groups[civ_id].add_civ_bonus(bonus) - - for unit_line in full_data_set.unit_lines.values(): - if unit_line.is_unique(): - head_unit_id = unit_line.get_head_unit_id() - head_unit_connection = full_data_set.unit_connections[head_unit_id] - enabling_research_id = head_unit_connection["enabling_research"].value - enabling_research = full_data_set.genie_techs[enabling_research_id] - enabling_civ_id = enabling_research["civilization_id"].value - - full_data_set.civ_groups[enabling_civ_id].add_unique_entity(unit_line) - - for building_line in full_data_set.building_lines.values(): - if building_line.is_unique(): - head_unit_id = building_line.get_head_unit_id() - head_building_connection = full_data_set.building_connections[head_unit_id] - enabling_research_id = head_building_connection["enabling_research"].value - enabling_research = full_data_set.genie_techs[enabling_research_id] - enabling_civ_id = enabling_research["civilization_id"].value - - full_data_set.civ_groups[enabling_civ_id].add_unique_entity(building_line) - - for tech_group in full_data_set.tech_groups.values(): - if tech_group.is_unique() and tech_group.is_researchable(): - civ_id = tech_group.get_civilization() - full_data_set.civ_groups[civ_id].add_unique_tech(tech_group) - - @staticmethod - def link_gatherers_to_dropsites(full_data_set: GenieObjectContainer) -> None: - """ - Link gatherers to the buildings they drop resources off. This is done - to provide quick access during conversion. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - villager_groups = full_data_set.villager_groups - - for villager in villager_groups.values(): - for unit in villager.variants[0].line: - drop_site_members = unit["drop_sites"].value - unit_id = unit["id0"].value - - for drop_site_member in drop_site_members: - drop_site_id = drop_site_member.value - - if drop_site_id > -1: - drop_site = full_data_set.building_lines[drop_site_id] - drop_site.add_gatherer_id(unit_id) - - @staticmethod - def link_garrison(full_data_set: GenieObjectContainer) -> None: - """ - Link a garrison unit to the lines that are stored and vice versa. This is done - to provide quick access during conversion. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - garrisoned_lines = {} - garrisoned_lines.update(full_data_set.unit_lines) - garrisoned_lines.update(full_data_set.ambient_groups) - - garrison_lines = {} - garrison_lines.update(full_data_set.unit_lines) - garrison_lines.update(full_data_set.building_lines) - - # Search through all units and look at their garrison commands - for unit_line in garrisoned_lines.values(): - garrison_classes = [] - garrison_units = [] - - if unit_line.has_command(3): - unit_commands = unit_line.get_head_unit()["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - if type_id != 3: - continue - - class_id = command["class_id"].value - if class_id > -1: - garrison_classes.append(class_id) - - if class_id == 3: - # Towers because Ensemble didn't like consistent rules - garrison_classes.append(52) - - unit_id = command["unit_id"].value - if unit_id > -1: - garrison_units.append(unit_id) - - for garrison_line in garrison_lines.values(): - if not garrison_line.is_garrison(): - continue - - # Natural garrison - garrison_mode = garrison_line.get_garrison_mode() - if garrison_mode == GenieGarrisonMode.NATURAL: - if unit_line.get_head_unit().has_member("creatable_type"): - creatable_type = unit_line.get_head_unit()["creatable_type"].value - - else: - creatable_type = 0 - - if garrison_line.get_head_unit().has_member("garrison_type"): - garrison_type = garrison_line.get_head_unit()["garrison_type"].value - - else: - garrison_type = 0 - - if creatable_type == 1 and not garrison_type & 0x01: - continue - - if creatable_type == 2 and not garrison_type & 0x02: - continue - - if creatable_type == 3 and not garrison_type & 0x04: - continue - - if creatable_type == 6 and not garrison_type & 0x08: - continue - - if garrison_line.get_class_id() in garrison_classes: - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - continue - - if garrison_line.get_head_unit_id() in garrison_units: - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - continue - - # Transports/ unit garrisons (no conditions) - elif garrison_mode in (GenieGarrisonMode.TRANSPORT, - GenieGarrisonMode.UNIT_GARRISON): - if garrison_line.get_class_id() in garrison_classes: - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - - # Self produced units (these cannot be determined from commands) - elif garrison_mode == GenieGarrisonMode.SELF_PRODUCED: - if unit_line in garrison_line.creates: - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - - # Monk inventories - elif garrison_mode == GenieGarrisonMode.MONK: - # Search for a pickup command - unit_commands = garrison_line.get_head_unit()["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - if type_id != 132: - continue - - unit_id = command["unit_id"].value - if unit_id == unit_line.get_head_unit_id(): - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - - @staticmethod - def link_trade_posts(full_data_set: GenieObjectContainer) -> None: - """ - Link a trade post building to the lines that it trades with. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - unit_lines = full_data_set.unit_lines.values() - - for unit_line in unit_lines: - if unit_line.has_command(111): - head_unit = unit_line.get_head_unit() - unit_commands = head_unit["unit_commands"].value - trade_post_id = -1 - for command in unit_commands: - # Find the trade command and the trade post id - type_id = command["type"].value - - if type_id != 111: - continue - - trade_post_id = command["unit_id"].value - - # Notify buiding - if trade_post_id in full_data_set.building_lines.keys(): - full_data_set.building_lines[trade_post_id].add_trading_line(unit_line) - - @staticmethod - def link_repairables(full_data_set: GenieObjectContainer) -> None: - """ - Set units/buildings as repairable - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - villager_groups = full_data_set.villager_groups - - repair_lines = {} - repair_lines.update(full_data_set.unit_lines) - repair_lines.update(full_data_set.building_lines) - - repair_classes = [] - for villager in villager_groups.values(): - repair_unit = villager.get_units_with_command(106)[0] - unit_commands = repair_unit["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - if type_id != 106: - continue - - class_id = command["class_id"].value - if class_id == -1: - # Buildings/Siege - repair_classes.append(3) - repair_classes.append(13) - repair_classes.append(52) - repair_classes.append(54) - repair_classes.append(55) - - else: - repair_classes.append(class_id) - - for repair_line in repair_lines.values(): - if repair_line.get_class_id() in repair_classes: - repair_line.repairable = True From ba022f35bbe3a604232fc3ea6aff66ecff46ca49 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 1 Jun 2025 01:38:22 +0200 Subject: [PATCH 120/163] convert: Refactor AoCTechSubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../conversion/aoc/tech/CMakeLists.txt | 9 + .../processor/conversion/aoc/tech/__init__.py | 5 + .../conversion/aoc/tech/attribute_modify.py | 83 +++ .../conversion/aoc/tech/resource_modify.py | 57 ++ .../conversion/aoc/tech/tech_cost.py | 136 +++++ .../conversion/aoc/tech/tech_time.py | 109 ++++ .../conversion/aoc/tech/unit_upgrade.py | 134 +++++ .../conversion/aoc/tech/upgrade_funcs.py | 83 +++ .../conversion/aoc/tech_subprocessor.py | 545 +----------------- 10 files changed, 646 insertions(+), 516 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/tech/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/tech/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/tech/attribute_modify.py create mode 100644 openage/convert/processor/conversion/aoc/tech/resource_modify.py create mode 100644 openage/convert/processor/conversion/aoc/tech/tech_cost.py create mode 100644 openage/convert/processor/conversion/aoc/tech/tech_time.py create mode 100644 openage/convert/processor/conversion/aoc/tech/unit_upgrade.py create mode 100644 openage/convert/processor/conversion/aoc/tech/upgrade_funcs.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index 856eb016da..90ad3b7113 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -28,3 +28,4 @@ add_subdirectory(modpack) add_subdirectory(nyan) add_subdirectory(pregen) add_subdirectory(resistance) +add_subdirectory(tech) diff --git a/openage/convert/processor/conversion/aoc/tech/CMakeLists.txt b/openage/convert/processor/conversion/aoc/tech/CMakeLists.txt new file mode 100644 index 0000000000..71d8a525eb --- /dev/null +++ b/openage/convert/processor/conversion/aoc/tech/CMakeLists.txt @@ -0,0 +1,9 @@ +add_py_modules( + __init__.py + attribute_modify.py + resource_modify.py + tech_cost.py + tech_time.py + unit_upgrade.py + upgrade_funcs.py +) diff --git a/openage/convert/processor/conversion/aoc/tech/__init__.py b/openage/convert/processor/conversion/aoc/tech/__init__.py new file mode 100644 index 0000000000..5574636013 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/tech/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create nyan patches for techs in AoC. +""" diff --git a/openage/convert/processor/conversion/aoc/tech/attribute_modify.py b/openage/convert/processor/conversion/aoc/tech/attribute_modify.py new file mode 100644 index 0000000000..f85c6cd861 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/tech/attribute_modify.py @@ -0,0 +1,83 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying attributes of entities. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....value_object.conversion.forward_ref import ForwardRef +from .upgrade_funcs import UPGRADE_ATTRIBUTE_FUNCS + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + + +def attribute_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying attributes of entities. + """ + patches = [] + dataset = converter_group.data + + effect_type = effect.get_type() + operator = None + if effect_type in (0, 10): + operator = MemberOperator.ASSIGN + + elif effect_type in (4, 14): + operator = MemberOperator.ADD + + elif effect_type in (5, 15): + operator = MemberOperator.MULTIPLY + + else: + raise TypeError(f"Effect type {effect_type} is not a valid attribute effect") + + unit_id = effect["attr_a"].value + class_id = effect["attr_b"].value + attribute_type = effect["attr_c"].value + value = effect["attr_d"].value + + if attribute_type == -1: + return patches + + affected_entities = [] + if unit_id != -1: + entity_lines = {} + entity_lines.update(dataset.unit_lines) + entity_lines.update(dataset.building_lines) + entity_lines.update(dataset.ambient_groups) + + for line in entity_lines.values(): + if line.contains_entity(unit_id): + affected_entities.append(line) + + elif attribute_type == 19: + if line.is_projectile_shooter() and line.has_projectile(unit_id): + affected_entities.append(line) + + elif class_id != -1: + entity_lines = {} + entity_lines.update(dataset.unit_lines) + entity_lines.update(dataset.building_lines) + entity_lines.update(dataset.ambient_groups) + + for line in entity_lines.values(): + if line.get_class_id() == class_id: + affected_entities.append(line) + + else: + return patches + + upgrade_func = UPGRADE_ATTRIBUTE_FUNCS[attribute_type] + for affected_entity in affected_entities: + patches.extend(upgrade_func(converter_group, affected_entity, value, operator, team)) + + return patches diff --git a/openage/convert/processor/conversion/aoc/tech/resource_modify.py b/openage/convert/processor/conversion/aoc/tech/resource_modify.py new file mode 100644 index 0000000000..65fef65846 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/tech/resource_modify.py @@ -0,0 +1,57 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying resources. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....value_object.conversion.forward_ref import ForwardRef +from .upgrade_funcs import UPGRADE_RESOURCE_FUNCS + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + + +def resource_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying resources. + """ + patches = [] + + effect_type = effect.get_type() + operator = None + if effect_type in (1, 11): + mode = effect["attr_b"].value + + if mode == 0: + operator = MemberOperator.ASSIGN + + else: + operator = MemberOperator.ADD + + elif effect_type in (6, 16): + operator = MemberOperator.MULTIPLY + + else: + raise TypeError(f"Effect type {effect_type} is not a valid resource effect") + + resource_id = effect["attr_a"].value + value = effect["attr_d"].value + + if resource_id in (-1, 6, 21): + # -1 = invalid ID + # 6 = set current age (unused) + # 21 = tech count (unused) + return patches + + upgrade_func = UPGRADE_RESOURCE_FUNCS[resource_id] + patches.extend(upgrade_func(converter_group, value, operator, team)) + + return patches diff --git a/openage/convert/processor/conversion/aoc/tech/tech_cost.py b/openage/convert/processor/conversion/aoc/tech/tech_cost.py new file mode 100644 index 0000000000..c8cc0d43fa --- /dev/null +++ b/openage/convert/processor/conversion/aoc/tech/tech_cost.py @@ -0,0 +1,136 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying tech costs. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....value_object.conversion.forward_ref import ForwardRef +from .....service.conversion import internal_name_lookups + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + + +def tech_cost_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying tech costs. + """ + patches = [] + dataset = converter_group.data + + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + obj_name = tech_lookup_dict[obj_id][0] + + else: + obj_name = civ_lookup_dict[obj_id][0] + + tech_id = effect["attr_a"].value + resource_id = effect["attr_b"].value + mode = effect["attr_c"].value + amount = int(effect["attr_d"].value) + + if tech_id not in tech_lookup_dict: + # Skips some legacy techs from AoK such as the tech for bombard cannon + return patches + + tech_group = dataset.tech_groups[tech_id] + tech_name = tech_lookup_dict[tech_id][0] + + if resource_id == 0: + resource_name = "Food" + + elif resource_id == 1: + resource_name = "Wood" + + elif resource_id == 2: + resource_name = "Stone" + + elif resource_id == 3: + resource_name = "Gold" + + else: + raise ValueError("no valid resource ID found") + + # Check if the tech actually costs an amount of the defined resource + for resource_amount in tech_group.tech["research_resource_costs"].value: + cost_resource_id = resource_amount["type_id"].value + + if cost_resource_id == resource_id: + break + + else: + # Skip patch generation if no matching resource cost was found + return patches + + if mode == 0: + operator = MemberOperator.ASSIGN + + else: + operator = MemberOperator.ADD + + patch_target_ref = f"{tech_name}.ResearchableTech.{tech_name}Cost.{resource_name}Amount" + patch_target_forward_ref = ForwardRef(tech_group, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{tech_name}CostWrapper" + wrapper_ref = f"{tech_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{tech_name}Cost" + nyan_patch_ref = f"{tech_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + amount, + "engine.util.resource.ResourceAmount", + operator) + + if team: + team_property = dataset.pregen_nyan_objects[ + "util.patch.property.types.Team" + ].get_nyan_object() + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "engine.util.patch.Patch") + + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/tech/tech_time.py b/openage/convert/processor/conversion/aoc/tech/tech_time.py new file mode 100644 index 0000000000..0ddacc08a1 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/tech/tech_time.py @@ -0,0 +1,109 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying tech research times. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....value_object.conversion.forward_ref import ForwardRef +from .....service.conversion import internal_name_lookups + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + + +def tech_time_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying tech research times. + """ + patches = [] + dataset = converter_group.data + + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + obj_name = tech_lookup_dict[obj_id][0] + + else: + obj_name = civ_lookup_dict[obj_id][0] + + tech_id = effect["attr_a"].value + mode = effect["attr_c"].value + research_time = effect["attr_d"].value + + if tech_id not in tech_lookup_dict: + # Skips some legacy techs from AoK such as the tech for bombard cannon + return patches + + tech_group = dataset.tech_groups[tech_id] + tech_name = tech_lookup_dict[tech_id][0] + + if mode == 0: + operator = MemberOperator.ASSIGN + + else: + operator = MemberOperator.ADD + + patch_target_ref = f"{tech_name}.ResearchableTech" + patch_target_forward_ref = ForwardRef(tech_group, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{tech_name}ResearchTimeWrapper" + wrapper_ref = f"{tech_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{tech_name}ResearchTime" + nyan_patch_ref = f"{tech_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("research_time", + research_time, + "engine.util.research.ResearchableTech", + operator) + + if team: + team_property = dataset.pregen_nyan_objects[ + "util.patch.property.types.Team" + ].get_nyan_object() + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "engine.util.patch.Patch") + + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/tech/unit_upgrade.py b/openage/convert/processor/conversion/aoc/tech/unit_upgrade.py new file mode 100644 index 0000000000..ae25d411dd --- /dev/null +++ b/openage/convert/processor/conversion/aoc/tech/unit_upgrade.py @@ -0,0 +1,134 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for upgrading entities in a line. +""" +from __future__ import annotations +import typing + +from openage.log import warn +from .....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup, \ + GenieBuildingLineGroup +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..upgrade_ability_subprocessor import AoCUpgradeAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + + +def upgrade_unit_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject +) -> list[ForwardRef]: + """ + Creates the patches for upgrading entities in a line. + """ + patches = [] + tech_id = converter_group.get_id() + dataset = converter_group.data + + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + upgrade_source_id = effect["attr_a"].value + upgrade_target_id = effect["attr_b"].value + + if upgrade_source_id not in dataset.unit_ref.keys() or\ + upgrade_target_id not in dataset.unit_ref.keys(): + # Skip annexes or transform units + return patches + + line = dataset.unit_ref[upgrade_source_id] + upgrade_source_pos = line.get_unit_position(upgrade_source_id) + try: + upgrade_target_pos = line.get_unit_position(upgrade_target_id) + + except KeyError: + # TODO: Implement branching line upgrades + warn(f"Could not create upgrade from unit {upgrade_source_id} to {upgrade_target_id}") + return patches + + if isinstance(line, GenieBuildingLineGroup): + # Building upgrades always reference the head unit + # so we use the decremented target id instead + upgrade_source_pos = upgrade_target_pos - 1 + + elif upgrade_target_pos - upgrade_source_pos != 1: + # Skip effects that upgrades entities not next to each other in + # the line. + return patches + + upgrade_source = line.line[upgrade_source_pos] + upgrade_target = line.line[upgrade_target_pos] + tech_name = tech_lookup_dict[tech_id][0] + + diff = upgrade_source.diff(upgrade_target) + + patches.extend(AoCUpgradeAbilitySubprocessor.death_ability( + converter_group, line, tech_name, diff)) + patches.extend(AoCUpgradeAbilitySubprocessor.despawn_ability( + converter_group, line, tech_name, diff)) + patches.extend(AoCUpgradeAbilitySubprocessor.idle_ability( + converter_group, line, tech_name, diff)) + patches.extend(AoCUpgradeAbilitySubprocessor.live_ability( + converter_group, line, tech_name, diff)) + patches.extend(AoCUpgradeAbilitySubprocessor.los_ability( + converter_group, line, tech_name, diff)) + patches.extend(AoCUpgradeAbilitySubprocessor.named_ability( + converter_group, line, tech_name, diff)) + patches.extend(AoCUpgradeAbilitySubprocessor.resistance_ability( + converter_group, line, tech_name, diff)) + patches.extend(AoCUpgradeAbilitySubprocessor.selectable_ability( + converter_group, line, tech_name, diff)) + patches.extend(AoCUpgradeAbilitySubprocessor.turn_ability( + converter_group, line, tech_name, diff)) + + if line.is_projectile_shooter(): + patches.extend( + AoCUpgradeAbilitySubprocessor.shoot_projectile_ability( + converter_group, + line, + tech_name, + upgrade_source, + upgrade_target, + 7, + diff + ) + ) + + elif line.is_melee() or line.is_ranged(): + if line.has_command(7): + # Attack + patches.extend( + AoCUpgradeAbilitySubprocessor.apply_discrete_effect_ability( + converter_group, + line, + tech_name, + 7, + line.is_ranged(), + diff + ) + ) + + if isinstance(line, GenieUnitLineGroup): + patches.extend( + AoCUpgradeAbilitySubprocessor.move_ability( + converter_group, + line, + tech_name, + diff + ) + ) + + if isinstance(line, GenieBuildingLineGroup): + patches.extend( + AoCUpgradeAbilitySubprocessor.attribute_change_tracker_ability( + converter_group, + line, + tech_name, + diff + ) + ) + + return patches diff --git a/openage/convert/processor/conversion/aoc/tech/upgrade_funcs.py b/openage/convert/processor/conversion/aoc/tech/upgrade_funcs.py new file mode 100644 index 0000000000..e04223e7db --- /dev/null +++ b/openage/convert/processor/conversion/aoc/tech/upgrade_funcs.py @@ -0,0 +1,83 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Mappings of AoC upgrade IDs to their respective subprocessor functions. +""" + +from ..upgrade_attribute_subprocessor import AoCUpgradeAttributeSubprocessor +from ..upgrade_resource_subprocessor import AoCUpgradeResourceSubprocessor + +UPGRADE_ATTRIBUTE_FUNCS = { + 0: AoCUpgradeAttributeSubprocessor.hp_upgrade, + 1: AoCUpgradeAttributeSubprocessor.los_upgrade, + 2: AoCUpgradeAttributeSubprocessor.garrison_capacity_upgrade, + 3: AoCUpgradeAttributeSubprocessor.unit_size_x_upgrade, + 4: AoCUpgradeAttributeSubprocessor.unit_size_y_upgrade, + 5: AoCUpgradeAttributeSubprocessor.move_speed_upgrade, + 6: AoCUpgradeAttributeSubprocessor.rotation_speed_upgrade, + 8: AoCUpgradeAttributeSubprocessor.armor_upgrade, + 9: AoCUpgradeAttributeSubprocessor.attack_upgrade, + 10: AoCUpgradeAttributeSubprocessor.reload_time_upgrade, + 11: AoCUpgradeAttributeSubprocessor.accuracy_upgrade, + 12: AoCUpgradeAttributeSubprocessor.max_range_upgrade, + 13: AoCUpgradeAttributeSubprocessor.work_rate_upgrade, + 14: AoCUpgradeAttributeSubprocessor.carry_capacity_upgrade, + 16: AoCUpgradeAttributeSubprocessor.projectile_unit_upgrade, + 17: AoCUpgradeAttributeSubprocessor.graphics_angle_upgrade, + 18: AoCUpgradeAttributeSubprocessor.terrain_defense_upgrade, + 19: AoCUpgradeAttributeSubprocessor.ballistics_upgrade, + 20: AoCUpgradeAttributeSubprocessor.min_range_upgrade, + 21: AoCUpgradeAttributeSubprocessor.resource_storage_1_upgrade, + 22: AoCUpgradeAttributeSubprocessor.blast_radius_upgrade, + 23: AoCUpgradeAttributeSubprocessor.search_radius_upgrade, + 42: AoCUpgradeAttributeSubprocessor.standing_wonders_upgrade, + 46: AoCUpgradeAttributeSubprocessor.tribute_inefficiency_upgrade, + 48: AoCUpgradeAttributeSubprocessor.tc_available_upgrade, + 49: AoCUpgradeAttributeSubprocessor.gold_counter_upgrade, + 57: AoCUpgradeAttributeSubprocessor.kidnap_storage_upgrade, + 100: AoCUpgradeAttributeSubprocessor.resource_cost_upgrade, + 101: AoCUpgradeAttributeSubprocessor.creation_time_upgrade, + 102: AoCUpgradeAttributeSubprocessor.min_projectiles_upgrade, + 103: AoCUpgradeAttributeSubprocessor.cost_food_upgrade, + 104: AoCUpgradeAttributeSubprocessor.cost_wood_upgrade, + 105: AoCUpgradeAttributeSubprocessor.cost_gold_upgrade, + 106: AoCUpgradeAttributeSubprocessor.cost_stone_upgrade, + 107: AoCUpgradeAttributeSubprocessor.max_projectiles_upgrade, + 108: AoCUpgradeAttributeSubprocessor.garrison_heal_upgrade, +} + +UPGRADE_RESOURCE_FUNCS = { + 4: AoCUpgradeResourceSubprocessor.starting_population_space_upgrade, + 27: AoCUpgradeResourceSubprocessor.monk_conversion_upgrade, + 28: AoCUpgradeResourceSubprocessor.building_conversion_upgrade, + 32: AoCUpgradeResourceSubprocessor.bonus_population_upgrade, + 35: AoCUpgradeResourceSubprocessor.faith_recharge_rate_upgrade, + 36: AoCUpgradeResourceSubprocessor.farm_food_upgrade, + 46: AoCUpgradeResourceSubprocessor.tribute_inefficiency_upgrade, + 47: AoCUpgradeResourceSubprocessor.gather_gold_efficiency_upgrade, + 50: AoCUpgradeResourceSubprocessor.reveal_ally_upgrade, + 77: AoCUpgradeResourceSubprocessor.conversion_resistance_upgrade, + 78: AoCUpgradeResourceSubprocessor.trade_penalty_upgrade, + 79: AoCUpgradeResourceSubprocessor.gather_stone_efficiency_upgrade, + 84: AoCUpgradeResourceSubprocessor.starting_villagers_upgrade, + 85: AoCUpgradeResourceSubprocessor.chinese_tech_discount_upgrade, + 86: AoCUpgradeResourceSubprocessor.research_time_upgrade, + 89: AoCUpgradeResourceSubprocessor.heal_rate_upgrade, + 90: AoCUpgradeResourceSubprocessor.heal_range_upgrade, + 91: AoCUpgradeResourceSubprocessor.starting_food_upgrade, + 92: AoCUpgradeResourceSubprocessor.starting_wood_upgrade, + 94: AoCUpgradeResourceSubprocessor.starting_gold_upgrade, + 96: AoCUpgradeResourceSubprocessor.berserk_heal_rate_upgrade, + 97: AoCUpgradeResourceSubprocessor.herding_dominance_upgrade, + 178: AoCUpgradeResourceSubprocessor.conversion_resistance_min_rounds_upgrade, + 179: AoCUpgradeResourceSubprocessor.conversion_resistance_max_rounds_upgrade, + 183: AoCUpgradeResourceSubprocessor.reveal_enemy_upgrade, + 189: AoCUpgradeResourceSubprocessor.gather_wood_efficiency_upgrade, + 190: AoCUpgradeResourceSubprocessor.gather_food_efficiency_upgrade, + 191: AoCUpgradeResourceSubprocessor.relic_gold_bonus_upgrade, + 192: AoCUpgradeResourceSubprocessor.heresy_upgrade, + 193: AoCUpgradeResourceSubprocessor.theocracy_upgrade, + 194: AoCUpgradeResourceSubprocessor.crenellations_upgrade, + 196: AoCUpgradeResourceSubprocessor.wonder_time_increase_upgrade, + 197: AoCUpgradeResourceSubprocessor.spies_discount_upgrade, +} diff --git a/openage/convert/processor/conversion/aoc/tech_subprocessor.py b/openage/convert/processor/conversion/aoc/tech_subprocessor.py index 3bcd64a55b..78c8e9a523 100644 --- a/openage/convert/processor/conversion/aoc/tech_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/tech_subprocessor.py @@ -1,9 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-statements,too-many-branches -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates patches for technologies. @@ -11,23 +6,18 @@ from __future__ import annotations import typing - -from openage.log import warn -from .....nyan.nyan_structs import MemberOperator -from ....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup, \ - CivTeamBonus, CivBonus -from ....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup, \ - GenieBuildingLineGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups +from ....entity_object.conversion.aoc.genie_tech import CivTeamBonus, CivBonus from ....value_object.conversion.forward_ref import ForwardRef -from .upgrade_ability_subprocessor import AoCUpgradeAbilitySubprocessor -from .upgrade_attribute_subprocessor import AoCUpgradeAttributeSubprocessor -from .upgrade_resource_subprocessor import AoCUpgradeResourceSubprocessor + +from .tech.attribute_modify import attribute_modify_effect +from .tech.resource_modify import resource_modify_effect +from .tech.tech_cost import tech_cost_modify_effect +from .tech.tech_time import tech_time_modify_effect +from .tech.unit_upgrade import upgrade_unit_effect +from .tech.upgrade_funcs import UPGRADE_ATTRIBUTE_FUNCS, UPGRADE_RESOURCE_FUNCS if typing.TYPE_CHECKING: from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_effect import GenieEffectObject class AoCTechSubprocessor: @@ -35,80 +25,8 @@ class AoCTechSubprocessor: Creates raw API objects and patches for techs and civ setups in AoC. """ - upgrade_attribute_funcs = { - 0: AoCUpgradeAttributeSubprocessor.hp_upgrade, - 1: AoCUpgradeAttributeSubprocessor.los_upgrade, - 2: AoCUpgradeAttributeSubprocessor.garrison_capacity_upgrade, - 3: AoCUpgradeAttributeSubprocessor.unit_size_x_upgrade, - 4: AoCUpgradeAttributeSubprocessor.unit_size_y_upgrade, - 5: AoCUpgradeAttributeSubprocessor.move_speed_upgrade, - 6: AoCUpgradeAttributeSubprocessor.rotation_speed_upgrade, - 8: AoCUpgradeAttributeSubprocessor.armor_upgrade, - 9: AoCUpgradeAttributeSubprocessor.attack_upgrade, - 10: AoCUpgradeAttributeSubprocessor.reload_time_upgrade, - 11: AoCUpgradeAttributeSubprocessor.accuracy_upgrade, - 12: AoCUpgradeAttributeSubprocessor.max_range_upgrade, - 13: AoCUpgradeAttributeSubprocessor.work_rate_upgrade, - 14: AoCUpgradeAttributeSubprocessor.carry_capacity_upgrade, - 16: AoCUpgradeAttributeSubprocessor.projectile_unit_upgrade, - 17: AoCUpgradeAttributeSubprocessor.graphics_angle_upgrade, - 18: AoCUpgradeAttributeSubprocessor.terrain_defense_upgrade, - 19: AoCUpgradeAttributeSubprocessor.ballistics_upgrade, - 20: AoCUpgradeAttributeSubprocessor.min_range_upgrade, - 21: AoCUpgradeAttributeSubprocessor.resource_storage_1_upgrade, - 22: AoCUpgradeAttributeSubprocessor.blast_radius_upgrade, - 23: AoCUpgradeAttributeSubprocessor.search_radius_upgrade, - 42: AoCUpgradeAttributeSubprocessor.standing_wonders_upgrade, - 46: AoCUpgradeAttributeSubprocessor.tribute_inefficiency_upgrade, - 48: AoCUpgradeAttributeSubprocessor.tc_available_upgrade, - 49: AoCUpgradeAttributeSubprocessor.gold_counter_upgrade, - 57: AoCUpgradeAttributeSubprocessor.kidnap_storage_upgrade, - 100: AoCUpgradeAttributeSubprocessor.resource_cost_upgrade, - 101: AoCUpgradeAttributeSubprocessor.creation_time_upgrade, - 102: AoCUpgradeAttributeSubprocessor.min_projectiles_upgrade, - 103: AoCUpgradeAttributeSubprocessor.cost_food_upgrade, - 104: AoCUpgradeAttributeSubprocessor.cost_wood_upgrade, - 105: AoCUpgradeAttributeSubprocessor.cost_gold_upgrade, - 106: AoCUpgradeAttributeSubprocessor.cost_stone_upgrade, - 107: AoCUpgradeAttributeSubprocessor.max_projectiles_upgrade, - 108: AoCUpgradeAttributeSubprocessor.garrison_heal_upgrade, - } - - upgrade_resource_funcs = { - 4: AoCUpgradeResourceSubprocessor.starting_population_space_upgrade, - 27: AoCUpgradeResourceSubprocessor.monk_conversion_upgrade, - 28: AoCUpgradeResourceSubprocessor.building_conversion_upgrade, - 32: AoCUpgradeResourceSubprocessor.bonus_population_upgrade, - 35: AoCUpgradeResourceSubprocessor.faith_recharge_rate_upgrade, - 36: AoCUpgradeResourceSubprocessor.farm_food_upgrade, - 46: AoCUpgradeResourceSubprocessor.tribute_inefficiency_upgrade, - 47: AoCUpgradeResourceSubprocessor.gather_gold_efficiency_upgrade, - 50: AoCUpgradeResourceSubprocessor.reveal_ally_upgrade, - 77: AoCUpgradeResourceSubprocessor.conversion_resistance_upgrade, - 78: AoCUpgradeResourceSubprocessor.trade_penalty_upgrade, - 79: AoCUpgradeResourceSubprocessor.gather_stone_efficiency_upgrade, - 84: AoCUpgradeResourceSubprocessor.starting_villagers_upgrade, - 85: AoCUpgradeResourceSubprocessor.chinese_tech_discount_upgrade, - 86: AoCUpgradeResourceSubprocessor.research_time_upgrade, - 89: AoCUpgradeResourceSubprocessor.heal_rate_upgrade, - 90: AoCUpgradeResourceSubprocessor.heal_range_upgrade, - 91: AoCUpgradeResourceSubprocessor.starting_food_upgrade, - 92: AoCUpgradeResourceSubprocessor.starting_wood_upgrade, - 94: AoCUpgradeResourceSubprocessor.starting_gold_upgrade, - 96: AoCUpgradeResourceSubprocessor.berserk_heal_rate_upgrade, - 97: AoCUpgradeResourceSubprocessor.herding_dominance_upgrade, - 178: AoCUpgradeResourceSubprocessor.conversion_resistance_min_rounds_upgrade, - 179: AoCUpgradeResourceSubprocessor.conversion_resistance_max_rounds_upgrade, - 183: AoCUpgradeResourceSubprocessor.reveal_enemy_upgrade, - 189: AoCUpgradeResourceSubprocessor.gather_wood_efficiency_upgrade, - 190: AoCUpgradeResourceSubprocessor.gather_food_efficiency_upgrade, - 191: AoCUpgradeResourceSubprocessor.relic_gold_bonus_upgrade, - 192: AoCUpgradeResourceSubprocessor.heresy_upgrade, - 193: AoCUpgradeResourceSubprocessor.theocracy_upgrade, - 194: AoCUpgradeResourceSubprocessor.crenellations_upgrade, - 196: AoCUpgradeResourceSubprocessor.wonder_time_increase_upgrade, - 197: AoCUpgradeResourceSubprocessor.spies_discount_upgrade, - } + upgrade_attribute_funcs = UPGRADE_ATTRIBUTE_FUNCS + upgrade_resource_funcs = UPGRADE_RESOURCE_FUNCS @classmethod def get_patches(cls, converter_group: ConverterObjectGroup) -> list[ForwardRef]: @@ -145,447 +63,42 @@ def get_patches(cls, converter_group: ConverterObjectGroup) -> list[ForwardRef]: type_id -= 10 if type_id in (0, 4, 5): - patches.extend(cls.attribute_modify_effect(converter_group, - effect, - team=team_effect)) + patches.extend(attribute_modify_effect(converter_group, + effect, + team=team_effect)) elif type_id in (1, 6): - patches.extend(cls.resource_modify_effect(converter_group, - effect, - team=team_effect)) + patches.extend(resource_modify_effect(converter_group, + effect, + team=team_effect)) elif type_id == 2: # Enabling/disabling units: Handled in creatable conditions pass elif type_id == 3: - patches.extend(cls.upgrade_unit_effect(converter_group, effect)) + patches.extend(upgrade_unit_effect(converter_group, effect)) elif type_id == 101: - patches.extend(cls.tech_cost_modify_effect(converter_group, - effect, - team=team_effect)) + patches.extend(tech_cost_modify_effect(converter_group, + effect, + team=team_effect)) elif type_id == 102: # Tech disable: Only used for civ tech tree pass elif type_id == 103: - patches.extend(cls.tech_time_modify_effect(converter_group, - effect, - team=team_effect)) + patches.extend(tech_time_modify_effect(converter_group, + effect, + team=team_effect)) team_effect = False return patches - @staticmethod - def attribute_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying attributes of entities. - """ - patches = [] - dataset = converter_group.data - - effect_type = effect.get_type() - operator = None - if effect_type in (0, 10): - operator = MemberOperator.ASSIGN - - elif effect_type in (4, 14): - operator = MemberOperator.ADD - - elif effect_type in (5, 15): - operator = MemberOperator.MULTIPLY - - else: - raise TypeError(f"Effect type {effect_type} is not a valid attribute effect") - - unit_id = effect["attr_a"].value - class_id = effect["attr_b"].value - attribute_type = effect["attr_c"].value - value = effect["attr_d"].value - - if attribute_type == -1: - return patches - - affected_entities = [] - if unit_id != -1: - entity_lines = {} - entity_lines.update(dataset.unit_lines) - entity_lines.update(dataset.building_lines) - entity_lines.update(dataset.ambient_groups) - - for line in entity_lines.values(): - if line.contains_entity(unit_id): - affected_entities.append(line) - - elif attribute_type == 19: - if line.is_projectile_shooter() and line.has_projectile(unit_id): - affected_entities.append(line) - - elif class_id != -1: - entity_lines = {} - entity_lines.update(dataset.unit_lines) - entity_lines.update(dataset.building_lines) - entity_lines.update(dataset.ambient_groups) - - for line in entity_lines.values(): - if line.get_class_id() == class_id: - affected_entities.append(line) - - else: - return patches - - upgrade_func = AoCTechSubprocessor.upgrade_attribute_funcs[attribute_type] - for affected_entity in affected_entities: - patches.extend(upgrade_func(converter_group, affected_entity, value, operator, team)) - - return patches - - @staticmethod - def resource_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying resources. - """ - patches = [] - - effect_type = effect.get_type() - operator = None - if effect_type in (1, 11): - mode = effect["attr_b"].value - - if mode == 0: - operator = MemberOperator.ASSIGN - - else: - operator = MemberOperator.ADD - - elif effect_type in (6, 16): - operator = MemberOperator.MULTIPLY - - else: - raise TypeError(f"Effect type {effect_type} is not a valid resource effect") - - resource_id = effect["attr_a"].value - value = effect["attr_d"].value - - if resource_id in (-1, 6, 21): - # -1 = invalid ID - # 6 = set current age (unused) - # 21 = tech count (unused) - return patches - - upgrade_func = AoCTechSubprocessor.upgrade_resource_funcs[resource_id] - patches.extend(upgrade_func(converter_group, value, operator, team)) - - return patches - - @staticmethod - def upgrade_unit_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject - ) -> list[ForwardRef]: - """ - Creates the patches for upgrading entities in a line. - """ - patches = [] - tech_id = converter_group.get_id() - dataset = converter_group.data - - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - upgrade_source_id = effect["attr_a"].value - upgrade_target_id = effect["attr_b"].value - - if upgrade_source_id not in dataset.unit_ref.keys() or\ - upgrade_target_id not in dataset.unit_ref.keys(): - # Skip annexes or transform units - return patches - - line = dataset.unit_ref[upgrade_source_id] - upgrade_source_pos = line.get_unit_position(upgrade_source_id) - try: - upgrade_target_pos = line.get_unit_position(upgrade_target_id) - - except KeyError: - # TODO: Implement branching line upgrades - warn(f"Could not create upgrade from unit {upgrade_source_id} to {upgrade_target_id}") - return patches - - if isinstance(line, GenieBuildingLineGroup): - # Building upgrades always reference the head unit - # so we use the decremented target id instead - upgrade_source_pos = upgrade_target_pos - 1 - - elif upgrade_target_pos - upgrade_source_pos != 1: - # Skip effects that upgrades entities not next to each other in - # the line. - return patches - - upgrade_source = line.line[upgrade_source_pos] - upgrade_target = line.line[upgrade_target_pos] - tech_name = tech_lookup_dict[tech_id][0] - - diff = upgrade_source.diff(upgrade_target) - - patches.extend(AoCUpgradeAbilitySubprocessor.death_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.despawn_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.idle_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.live_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.los_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.named_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.resistance_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.selectable_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.turn_ability( - converter_group, line, tech_name, diff)) - - if line.is_projectile_shooter(): - patches.extend(AoCUpgradeAbilitySubprocessor.shoot_projectile_ability(converter_group, line, - tech_name, - upgrade_source, - upgrade_target, - 7, diff)) - elif line.is_melee() or line.is_ranged(): - if line.has_command(7): - # Attack - patches.extend(AoCUpgradeAbilitySubprocessor.apply_discrete_effect_ability(converter_group, - line, tech_name, - 7, - line.is_ranged(), - diff)) - - if isinstance(line, GenieUnitLineGroup): - patches.extend(AoCUpgradeAbilitySubprocessor.move_ability(converter_group, line, - tech_name, diff)) - - if isinstance(line, GenieBuildingLineGroup): - patches.extend(AoCUpgradeAbilitySubprocessor.attribute_change_tracker_ability(converter_group, line, - tech_name, diff)) - - return patches - - @staticmethod - def tech_cost_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying tech costs. - """ - patches = [] - dataset = converter_group.data - - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - obj_name = tech_lookup_dict[obj_id][0] - - else: - obj_name = civ_lookup_dict[obj_id][0] - - tech_id = effect["attr_a"].value - resource_id = effect["attr_b"].value - mode = effect["attr_c"].value - amount = int(effect["attr_d"].value) - - if tech_id not in tech_lookup_dict: - # Skips some legacy techs from AoK such as the tech for bombard cannon - return patches - - tech_group = dataset.tech_groups[tech_id] - tech_name = tech_lookup_dict[tech_id][0] - - if resource_id == 0: - resource_name = "Food" - - elif resource_id == 1: - resource_name = "Wood" - - elif resource_id == 2: - resource_name = "Stone" - - elif resource_id == 3: - resource_name = "Gold" - - else: - raise ValueError("no valid resource ID found") - - # Check if the tech actually costs an amount of the defined resource - for resource_amount in tech_group.tech["research_resource_costs"].value: - cost_resource_id = resource_amount["type_id"].value - - if cost_resource_id == resource_id: - break - - else: - # Skip patch generation if no matching resource cost was found - return patches - - if mode == 0: - operator = MemberOperator.ASSIGN - - else: - operator = MemberOperator.ADD - - patch_target_ref = f"{tech_name}.ResearchableTech.{tech_name}Cost.{resource_name}Amount" - patch_target_forward_ref = ForwardRef(tech_group, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{tech_name}CostWrapper" - wrapper_ref = f"{tech_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{tech_name}Cost" - nyan_patch_ref = f"{tech_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - amount, - "engine.util.resource.ResourceAmount", - operator) - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "engine.util.patch.Patch") - - 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) - - return patches - - @staticmethod - def tech_time_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying tech research times. - """ - patches = [] - dataset = converter_group.data - - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - obj_name = tech_lookup_dict[obj_id][0] - - else: - obj_name = civ_lookup_dict[obj_id][0] - - tech_id = effect["attr_a"].value - mode = effect["attr_c"].value - research_time = effect["attr_d"].value - - if tech_id not in tech_lookup_dict: - # Skips some legacy techs from AoK such as the tech for bombard cannon - return patches - - tech_group = dataset.tech_groups[tech_id] - tech_name = tech_lookup_dict[tech_id][0] - - if mode == 0: - operator = MemberOperator.ASSIGN - - else: - operator = MemberOperator.ADD - - patch_target_ref = f"{tech_name}.ResearchableTech" - patch_target_forward_ref = ForwardRef(tech_group, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{tech_name}ResearchTimeWrapper" - wrapper_ref = f"{tech_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{tech_name}ResearchTime" - nyan_patch_ref = f"{tech_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("research_time", - research_time, - "engine.util.research.ResearchableTech", - operator) - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "engine.util.patch.Patch") - - 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) - - return patches + attribute_modify_effect = staticmethod(attribute_modify_effect) + resource_modify_effect = staticmethod(resource_modify_effect) + upgrade_unit_effect = staticmethod(upgrade_unit_effect) + tech_cost_modify_effect = staticmethod(tech_cost_modify_effect) + tech_time_modify_effect = staticmethod(tech_time_modify_effect) From be467387ffda79c11c1b3b1486e20c68da2ee4a5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 1 Jun 2025 23:40:27 +0200 Subject: [PATCH 121/163] convert: Refactor AoCUpgradeAbilitySubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 3 + .../processor/conversion/aoc/ability/util.py | 1 - .../aoc/upgrade_ability/CMakeLists.txt | 18 + .../aoc/upgrade_ability/__init__.py | 5 + .../apply_continuous_effect.py | 243 ++ .../upgrade_ability/apply_discrete_effect.py | 267 ++ .../attribute_change_tracker.py | 133 + .../conversion/aoc/upgrade_ability/death.py | 89 + .../conversion/aoc/upgrade_ability/despawn.py | 95 + .../conversion/aoc/upgrade_ability/idle.py | 89 + .../aoc/upgrade_ability/line_of_sight.py | 107 + .../conversion/aoc/upgrade_ability/live.py | 113 + .../conversion/aoc/upgrade_ability/move.py | 158 ++ .../conversion/aoc/upgrade_ability/named.py | 114 + .../aoc/upgrade_ability/resistance.py | 53 + .../aoc/upgrade_ability/selectable.py | 162 ++ .../aoc/upgrade_ability/shoot_projectile.py | 377 +++ .../conversion/aoc/upgrade_ability/turn.py | 117 + .../conversion/aoc/upgrade_ability/util.py | 358 +++ .../aoc/upgrade_ability_subprocessor.py | 2279 +---------------- .../aoc/upgrade_effect/CMakeLists.txt | 4 + .../conversion/aoc/upgrade_effect/__init__.py | 5 + .../conversion/aoc/upgrade_effect/attack.py | 305 +++ .../aoc/upgrade_effect_subprocessor.py | 576 +---- .../aoc/upgrade_resistance/CMakeLists.txt | 4 + .../aoc/upgrade_resistance/__init__.py | 5 + .../aoc/upgrade_resistance/attack.py | 288 +++ 27 files changed, 3154 insertions(+), 2814 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/apply_continuous_effect.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/apply_discrete_effect.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/attribute_change_tracker.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/death.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/despawn.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/idle.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/line_of_sight.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/live.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/move.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/named.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/resistance.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/selectable.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/shoot_projectile.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/turn.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_ability/util.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_effect/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/upgrade_effect/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_effect/attack.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resistance/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resistance/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resistance/attack.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index 90ad3b7113..6eb0daa02c 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -29,3 +29,6 @@ add_subdirectory(nyan) add_subdirectory(pregen) add_subdirectory(resistance) add_subdirectory(tech) +add_subdirectory(upgrade_ability) +add_subdirectory(upgrade_effect) +add_subdirectory(upgrade_resistance) diff --git a/openage/convert/processor/conversion/aoc/ability/util.py b/openage/convert/processor/conversion/aoc/ability/util.py index 5a11194da2..2650910a07 100644 --- a/openage/convert/processor/conversion/aoc/ability/util.py +++ b/openage/convert/processor/conversion/aoc/ability/util.py @@ -17,7 +17,6 @@ if typing.TYPE_CHECKING: from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup - from .....value_object.conversion.forward_ref import ForwardRef def create_animation( diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/CMakeLists.txt b/openage/convert/processor/conversion/aoc/upgrade_ability/CMakeLists.txt new file mode 100644 index 0000000000..cc6e9ce140 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/CMakeLists.txt @@ -0,0 +1,18 @@ +add_py_modules( + __init__.py + apply_continuous_effect.py + apply_discrete_effect.py + attribute_change_tracker.py + death.py + despawn.py + idle.py + line_of_sight.py + live.py + move.py + named.py + resistance.py + selectable.py + shoot_projectile.py + turn.py + util.py +) diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/__init__.py b/openage/convert/processor/conversion/aoc/upgrade_ability/__init__.py new file mode 100644 index 0000000000..d6d856f2a9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create nyan patches for upgrading abilities of AoC entities. +""" diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/apply_continuous_effect.py b/openage/convert/processor/conversion/aoc/upgrade_ability/apply_continuous_effect.py new file mode 100644 index 0000000000..31299141cc --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/apply_continuous_effect.py @@ -0,0 +1,243 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the ApplyContinuousEffect ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from .util import create_animation_patch, create_command_sound_patch + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def apply_continuous_effect_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + command_id: int, + ranged: bool = False, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the ApplyContinuousEffect ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param command_id: The command ID of the AoC command. + :param ranged: Whether the ability is a ranged attack. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + ability_name = command_lookup_dict[command_id][0] + + data_changed = False + diff_animation = diff["attack_sprite_id"] + diff_comm_sound = diff["command_sound_id"] + diff_frame_delay = diff["frame_delay"] + if any(not isinstance(value, NoDiffMember) for value in (diff_frame_delay)): + data_changed = True + + # Command types Heal, Construct, Repair are not upgraded by lines + + 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 + )): + 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 + + # Nyan patch + patch_target_ref = f"{game_entity_name}.{ability_name}" + nyan_patch_name = f"Change{game_entity_name}{ability_name}" + wrapper, anim_patch_forward_ref = create_animation_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_", + [diff_animation_id] + ) + patches.append(anim_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if not isinstance(diff_comm_sound, NoDiffMember): + diff_comm_sound_id = diff_comm_sound.value + + # Nyan patch + patch_target_ref = f"{game_entity_name}.{ability_name}" + nyan_patch_name = f"Change{game_entity_name}{ability_name}" + wrapper, sound_patch_forward_ref = create_command_sound_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_", + [diff_comm_sound_id] + ) + patches.append(sound_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if data_changed: + patch_target_ref = f"{game_entity_name}.{ability_name}" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{ability_name}Wrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_frame_delay, NoDiffMember): + if not isinstance(diff_animation, NoDiffMember): + attack_graphic_id = diff_animation.value + + else: + attack_graphic_id = diff_animation.ref.value + + attack_graphic = dataset.genie_graphics[attack_graphic_id] + frame_rate = attack_graphic.get_frame_rate() + frame_delay = diff_frame_delay.value + application_delay = frame_rate * frame_delay + + nyan_patch_raw_api_object.add_raw_patch_member( + "application_delay", + application_delay, + "engine.ability.type.ApplyContinuousEffect", + MemberOperator.ASSIGN + ) + + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/apply_discrete_effect.py b/openage/convert/processor/conversion/aoc/upgrade_ability/apply_discrete_effect.py new file mode 100644 index 0000000000..55140424ee --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/apply_discrete_effect.py @@ -0,0 +1,267 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the ApplyDiscreteEffect ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from ..upgrade_effect_subprocessor import AoCUpgradeEffectSubprocessor +from .util import create_animation_patch, create_command_sound_patch + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def apply_discrete_effect_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + command_id: int, + ranged: bool = False, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the ApplyDiscreteEffect ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + ability_name = command_lookup_dict[command_id][0] + + data_changed = False + diff_animation = diff["attack_sprite_id"] + diff_comm_sound = diff["command_sound_id"] + diff_reload_time = diff["attack_speed"] + diff_frame_delay = diff["frame_delay"] + if any(not isinstance(value, NoDiffMember) for value in (diff_reload_time, + diff_frame_delay)): + data_changed = True + + 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 + )): + 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 + + # Nyan patch + patch_target_ref = f"{game_entity_name}.{ability_name}" + nyan_patch_name = f"Change{game_entity_name}{ability_name}" + wrapper, anim_patch_forward_ref = create_animation_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_", + [diff_animation_id] + ) + patches.append(anim_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if not isinstance(diff_comm_sound, NoDiffMember): + diff_comm_sound_id = diff_comm_sound.value + + # Nyan patch + patch_target_ref = f"{game_entity_name}.{ability_name}" + nyan_patch_name = f"Change{game_entity_name}{ability_name}" + wrapper, sound_patch_forward_ref = create_command_sound_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_", + [diff_comm_sound_id] + ) + patches.append(sound_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if data_changed: + patch_target_ref = f"{game_entity_name}.{ability_name}" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{ability_name}Wrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_reload_time, NoDiffMember): + reload_time = diff_reload_time.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "reload_time", + reload_time, + "engine.ability.type.ApplyDiscreteEffect", + MemberOperator.ADD + ) + + if not isinstance(diff_frame_delay, NoDiffMember): + if not isinstance(diff_animation, NoDiffMember): + attack_graphic_id = diff_animation.value + + else: + attack_graphic_id = diff_animation.ref.value + + attack_graphic = dataset.genie_graphics[attack_graphic_id] + frame_rate = attack_graphic.get_frame_rate() + frame_delay = diff_frame_delay.value + application_delay = frame_rate * frame_delay + + nyan_patch_raw_api_object.add_raw_patch_member( + "application_delay", + application_delay, + "engine.ability.type.ApplyDiscreteEffect", + MemberOperator.ASSIGN + ) + + 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) + + # Seperate because effects get their own wrappers from the subprocessor + data_changed = False + diff_attacks = None + if not data_changed and command_id == 7: + diff_attacks = diff["attacks"] + if not isinstance(diff_attacks, NoDiffMember): + data_changed = True + + if data_changed: + patch_target_ref = f"{game_entity_name}.{ability_name}" + if command_id == 7 and not isinstance(diff_attacks, NoDiffMember): + patches.extend(AoCUpgradeEffectSubprocessor.get_attack_effects(converter_group, + line, diff, + patch_target_ref)) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/attribute_change_tracker.py b/openage/convert/processor/conversion/aoc/upgrade_ability/attribute_change_tracker.py new file mode 100644 index 0000000000..e3bf5df613 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/attribute_change_tracker.py @@ -0,0 +1,133 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the AttributeChangeTracker ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from .util import create_animation + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def attribute_change_tracker_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the AttributeChangeTracker ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if diff: + diff_damage_graphics = diff["damage_graphics"] + if isinstance(diff_damage_graphics, NoDiffMember): + return patches + + diff_damage_animations = diff_damage_graphics.value + + else: + return patches + + percentage = 0 + for diff_damage_animation in diff_damage_animations: + if isinstance(diff_damage_animation, NoDiffMember) or\ + isinstance(diff_damage_animation["graphic_id"], NoDiffMember): + continue + + # This should be a NoDiffMember + percentage = diff_damage_animation["damage_percent"].ref.value + + patch_target_ref = (f"{game_entity_name}.AttributeChangeTracker." + f"ChangeProgress{percentage}.AnimationOverlay") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}DamageGraphic{percentage}Wrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}DamageGraphic{str(percentage)}" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) + + animations_set = [] + diff_animation_id = diff_damage_animation["graphic_id"].value + if diff_animation_id > -1: + # Patch the new animation in + animation_forward_ref = create_animation( + converter_group, + line, + diff_animation_id, + nyan_patch_ref, + "Idle", + f"idle_damage_override_{percentage}_" + ) + animations_set.append(animation_forward_ref) + + nyan_patch_raw_api_object.add_raw_patch_member( + "overlays", + animations_set, + "engine.util.progress.property.type.AnimationOverlay", + MemberOperator.ASSIGN + ) + + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/death.py b/openage/convert/processor/conversion/aoc/upgrade_ability/death.py new file mode 100644 index 0000000000..cbd7ba36b8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/death.py @@ -0,0 +1,89 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the death ability (PassiveTransformTo). +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from .util import create_animation_patch + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def death_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the Death ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if diff: + diff_animation = diff["dying_graphic"] + if isinstance(diff_animation, NoDiffMember): + return patches + + # TODO: If the head unit has an invalid -1 graphic, it doesnt get the Animated + # property for the ability in the ability subprocessor, so + # we can't patch it here. + # + # We have to find a solution for this, e.g. patch in the Animated ability + # here or in the ability subprocessor. + if line.get_head_unit()["dying_graphic"].value == -1: + return patches + + diff_animation_id = diff_animation.value + + else: + return patches + + patch_target_ref = f"{game_entity_name}.Death" + nyan_patch_name = f"Change{game_entity_name}Death" + + # Nyan patch + wrapper, anim_patch_forward_ref = create_animation_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + "Death", + "death_", + [diff_animation_id] + ) + patches.append(anim_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/despawn.py b/openage/convert/processor/conversion/aoc/upgrade_ability/despawn.py new file mode 100644 index 0000000000..b56306a05c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/despawn.py @@ -0,0 +1,95 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the Despwan ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from .util import create_animation_patch + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def despawn_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the Despawn ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if diff: + diff_dead_unit = diff["dead_unit_id"] + if isinstance(diff_dead_unit, NoDiffMember): + return patches + + diff_animation_id = dataset.genie_units[diff_dead_unit.value]["idle_graphic0"].value + + # TODO: If the head unit has an invalid -1 graphic, it doesnt get the Animated + # property for the ability in the ability subprocessor, so + # we can't patch it here. + # + # We have to find a solution for this, e.g. patch in the Animated ability + # here or in the ability subprocessor. + dead_unit_id = line.get_head_unit()["dead_unit_id"].value + if dead_unit_id == -1: + return patches + + dead_unit = dataset.genie_units[dead_unit_id] + dead_unit_animation_id = dead_unit["idle_graphic0"].value + if dead_unit_animation_id == -1: + return patches + + else: + return patches + + patch_target_ref = f"{game_entity_name}.Despawn" + nyan_patch_name = f"Change{game_entity_name}Despawn" + + # Nyan patch + wrapper, anim_patch_forward_ref = create_animation_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + "Despawn", + "despawn_", + [diff_animation_id] + ) + patches.append(anim_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/idle.py b/openage/convert/processor/conversion/aoc/upgrade_ability/idle.py new file mode 100644 index 0000000000..e73fa34ab9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/idle.py @@ -0,0 +1,89 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the Idle ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from .util import create_animation_patch + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def idle_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the Idle ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if diff: + diff_animation = diff["idle_graphic0"] + if isinstance(diff_animation, NoDiffMember): + return patches + + # TODO: If the head unit has an invalid -1 graphic, it doesnt get the Animated + # property for the ability in the ability subprocessor, so + # we can't patch it here. + # + # We have to find a solution for this, e.g. patch in the Animated ability + # here or in the ability subprocessor. + if line.get_head_unit()["idle_graphic0"].value == -1: + return patches + + diff_animation_id = diff_animation.value + + else: + return patches + + patch_target_ref = f"{game_entity_name}.Idle" + nyan_patch_name = f"Change{game_entity_name}Idle" + + # Nyan patch + wrapper, anim_patch_forward_ref = create_animation_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + "Idle", + "idle_", + [diff_animation_id] + ) + patches.append(anim_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/line_of_sight.py b/openage/convert/processor/conversion/aoc/upgrade_ability/line_of_sight.py new file mode 100644 index 0000000000..d49edf926e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/line_of_sight.py @@ -0,0 +1,107 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the LineOfSight ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def line_of_sight_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the LineOfSight ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if diff: + diff_line_of_sight = diff["line_of_sight"] + if isinstance(diff_line_of_sight, NoDiffMember): + return patches + + diff_los_range = diff_line_of_sight.value + + else: + return patches + + patch_target_ref = f"{game_entity_name}.LineOfSight" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}LineOfSightWrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}LineOfSight" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) + + # Line of Sight + nyan_patch_raw_api_object.add_raw_patch_member("range", + diff_los_range, + "engine.ability.type.LineOfSight", + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/live.py b/openage/convert/processor/conversion/aoc/upgrade_ability/live.py new file mode 100644 index 0000000000..bd98298093 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/live.py @@ -0,0 +1,113 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the Live ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def live_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the Live ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if diff: + diff_hp = diff["hit_points"] + if isinstance(diff_hp, NoDiffMember): + return patches + + diff_hp_value = diff_hp.value + + else: + return patches + + patch_target_ref = f"{game_entity_name}.Live.Health" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}HealthWrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}Health" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) + + # HP max value + nyan_patch_raw_api_object.add_raw_patch_member("max_value", + diff_hp_value, + "engine.util.attribute.AttributeSetting", + MemberOperator.ADD) + + # HP starting value + nyan_patch_raw_api_object.add_raw_patch_member("starting_value", + diff_hp_value, + "engine.util.attribute.AttributeSetting", + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/move.py b/openage/convert/processor/conversion/aoc/upgrade_ability/move.py new file mode 100644 index 0000000000..b7991de8b3 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/move.py @@ -0,0 +1,158 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the Move ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from .util import create_animation_patch, create_command_sound_patch + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def move_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the Move ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + data_changed = False + diff_move_animation = diff["move_graphics"] + diff_comm_sound = diff["command_sound_id"] + diff_move_speed = diff["speed"] + if any(not isinstance(value, NoDiffMember) for value in (diff_move_speed,)): + data_changed = True + + if not isinstance(diff_move_animation, NoDiffMember): + diff_animation_id = diff_move_animation.value + + # Nyan patch + patch_target_ref = f"{game_entity_name}.Move" + nyan_patch_name = f"Change{game_entity_name}Move" + wrapper, anim_patch_forward_ref = create_animation_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + "Move", + "move_", + [diff_animation_id] + ) + patches.append(anim_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if not isinstance(diff_comm_sound, NoDiffMember): + diff_comm_sound_id = diff_comm_sound.value + + # Nyan patch + patch_target_ref = f"{game_entity_name}.Move" + nyan_patch_name = f"Change{game_entity_name}Move" + wrapper, sound_patch_forward_ref = create_command_sound_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + "Move", + "move_", + [diff_comm_sound_id] + ) + patches.append(sound_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if data_changed: + patch_target_ref = f"{game_entity_name}.Move" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}MoveWrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}Move" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_move_speed, NoDiffMember): + diff_speed_value = diff_move_speed.value + + nyan_patch_raw_api_object.add_raw_patch_member("speed", + diff_speed_value, + "engine.ability.type.Move", + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/named.py b/openage/convert/processor/conversion/aoc/upgrade_ability/named.py new file mode 100644 index 0000000000..555190c7b6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/named.py @@ -0,0 +1,114 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the Named ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from .util import create_language_strings + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def named_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the Named ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + group_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if isinstance(converter_group, GenieTechEffectBundleGroup): + obj_prefix = tech_lookup_dict[group_id][0] + + else: + obj_prefix = game_entity_name + + diff_name = diff["language_dll_name"] + if not isinstance(diff_name, NoDiffMember): + patch_target_ref = f"{game_entity_name}.Named.{game_entity_name}Name" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}NameWrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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[group_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}Name" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) + + name_string_id = diff_name.value + translations = create_language_strings( + converter_group, + name_string_id, + nyan_patch_ref, + f"{obj_prefix}Name" + ) + nyan_patch_raw_api_object.add_raw_patch_member( + "translations", + translations, + "engine.util.language.translated.type.TranslatedString", + MemberOperator.ASSIGN + ) + + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/resistance.py b/openage/convert/processor/conversion/aoc/upgrade_ability/resistance.py new file mode 100644 index 0000000000..b8c1f926ad --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/resistance.py @@ -0,0 +1,53 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the Resistance ability. +""" +from __future__ import annotations +import typing + +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from ..upgrade_effect_subprocessor import AoCUpgradeEffectSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def resistance_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the Resistance ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + diff_armors = diff["armors"] + if not isinstance(diff_armors, NoDiffMember): + patch_target_ref = f"{game_entity_name}.Resistance" + patches.extend(AoCUpgradeEffectSubprocessor.get_attack_resistances(converter_group, + line, diff, + patch_target_ref)) + + # TODO: Other resistance types + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/selectable.py b/openage/convert/processor/conversion/aoc/upgrade_ability/selectable.py new file mode 100644 index 0000000000..c8bbdcb1d9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/selectable.py @@ -0,0 +1,162 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the Selectable ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, \ + GenieUnitLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from .util import create_command_sound_patch + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def selectable_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the Selectable ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + # First patch: Sound for the SelectableSelf ability + changed = False + if diff: + diff_selection_sound = diff["selection_sound_id"] + if not isinstance(diff_selection_sound, NoDiffMember): + changed = True + + if isinstance(line, GenieUnitLineGroup): + ability_name = "SelectableSelf" + + else: + ability_name = "Selectable" + + if changed: + patch_target_ref = f"{game_entity_name}.{ability_name}" + nyan_patch_name = f"Change{game_entity_name}{ability_name}" + + # Change sound + diff_selection_sound_id = diff_selection_sound.value + # Nyan patch + wrapper, sound_patch_forward_ref = create_command_sound_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + ability_name, + "select_", + [diff_selection_sound_id] + ) + patches.append(sound_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + # Second patch: Selection box + changed = False + if diff: + diff_radius_x = diff["selection_shape_x"] + diff_radius_y = diff["selection_shape_y"] + if any(not isinstance(value, NoDiffMember) for value in (diff_radius_x, + diff_radius_y)): + changed = True + + if changed: + patch_target_ref = f"{game_entity_name}.{ability_name}.Rectangle" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{ability_name}RectangleWrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}Rectangle" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_radius_x, NoDiffMember): + diff_width_value = diff_radius_x.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "width", + diff_width_value, + "engine.util.selection_box.type.Rectangle", + MemberOperator.ADD + ) + + if not isinstance(diff_radius_y, NoDiffMember): + diff_height_value = diff_radius_y.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "height", + diff_height_value, + "engine.util.selection_box.type.Rectangle", + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/shoot_projectile.py b/openage/convert/processor/conversion/aoc/upgrade_ability/shoot_projectile.py new file mode 100644 index 0000000000..5df0859dc8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/shoot_projectile.py @@ -0,0 +1,377 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the ShootProjectile ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from .util import create_animation_patch, create_command_sound_patch + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieUnitObject + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def shoot_projectile_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + upgrade_source: GenieUnitObject, + upgrade_target: GenieUnitObject, + command_id: int, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the ShootProjectile ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + ability_name = command_lookup_dict[command_id][0] + + data_changed = False + + 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_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 + + # Nyan patch + patch_target_ref = f"{game_entity_name}.{ability_name}" + nyan_patch_name = f"Change{game_entity_name}{ability_name}" + wrapper, anim_patch_forward_ref = create_animation_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_", + [diff_animation_id] + ) + patches.append(anim_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if not isinstance(diff_comm_sound, NoDiffMember): + diff_comm_sound_id = diff_comm_sound.value + + # Nyan patch + patch_target_ref = f"{game_entity_name}.{ability_name}" + nyan_patch_name = f"Change{game_entity_name}{ability_name}" + wrapper, sound_patch_forward_ref = create_command_sound_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_", + [diff_comm_sound_id] + ) + patches.append(sound_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if data_changed: + patch_target_ref = f"{game_entity_name}.{ability_name}" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{ability_name}Wrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_projectiles, NoDiffMember): + min_projectiles = diff_min_projectiles.value + source_min_count = upgrade_source["projectile_min_count"].value + source_max_count = upgrade_source["projectile_max_count"].value + target_min_count = upgrade_target["projectile_min_count"].value + target_max_count = upgrade_target["projectile_max_count"].value + + # Account for a special case where the number of projectiles are 0 + # in the .dat, but the game still counts this as 1 when a projectile + # is defined. + if source_min_count == 0 and source_max_count == 0: + min_projectiles -= 1 + + if target_min_count == 0 and target_max_count == 0: + min_projectiles += 1 + + if min_projectiles != 0: + nyan_patch_raw_api_object.add_raw_patch_member( + "min_projectiles", + min_projectiles, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD + ) + + if not isinstance(diff_max_projectiles, NoDiffMember): + max_projectiles = diff_max_projectiles.value + source_min_count = upgrade_source["projectile_min_count"].value + source_max_count = upgrade_source["projectile_max_count"].value + target_min_count = upgrade_target["projectile_min_count"].value + target_max_count = upgrade_target["projectile_max_count"].value + + # Account for a special case where the number of projectiles are 0 + # in the .dat, but the game still counts this as 1 when a projectile + # is defined. + if source_min_count == 0 and source_max_count == 0: + max_projectiles -= 1 + + if target_min_count == 0 and target_max_count == 0: + max_projectiles += 1 + + if max_projectiles != 0: + nyan_patch_raw_api_object.add_raw_patch_member( + "max_projectiles", + max_projectiles, + "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", + reload_time, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD + ) + + if not isinstance(diff_spawn_delay, NoDiffMember): + if not isinstance(diff_animation, NoDiffMember): + attack_graphic_id = diff_animation.value + + else: + attack_graphic_id = diff_animation.ref.value + + attack_graphic = dataset.genie_graphics[attack_graphic_id] + frame_rate = attack_graphic.get_frame_rate() + frame_delay = diff_spawn_delay.value + spawn_delay = frame_rate * frame_delay + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawn_delay", + spawn_delay, + "engine.ability.type.ShootProjectile", + MemberOperator.ASSIGN + ) + + if not isinstance(diff_spawn_area_offsets, NoDiffMember): + diff_spawn_area_x = diff_spawn_area_offsets[0] + diff_spawn_area_y = diff_spawn_area_offsets[1] + diff_spawn_area_z = diff_spawn_area_offsets[2] + + if not isinstance(diff_spawn_area_x, NoDiffMember): + spawn_area_x = diff_spawn_area_x.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawning_area_offset_x", + spawn_area_x, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD + ) + + if not isinstance(diff_spawn_area_y, NoDiffMember): + spawn_area_y = diff_spawn_area_y.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawning_area_offset_y", + spawn_area_y, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD + ) + + if not isinstance(diff_spawn_area_z, NoDiffMember): + spawn_area_z = diff_spawn_area_z.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawning_area_offset_z", + spawn_area_z, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD + ) + + if not isinstance(diff_spawn_area_width, NoDiffMember): + spawn_area_width = diff_spawn_area_width.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawning_area_width", + spawn_area_width, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD + ) + + if not isinstance(diff_spawn_area_height, NoDiffMember): + spawn_area_height = diff_spawn_area_height.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawning_area_height", + spawn_area_height, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD + ) + + if not isinstance(diff_spawn_area_randomness, NoDiffMember): + spawn_area_randomness = diff_spawn_area_randomness.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawning_area_randomness", + spawn_area_randomness, + "engine.ability.type.ShootProjectile", + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/turn.py b/openage/convert/processor/conversion/aoc/upgrade_ability/turn.py new file mode 100644 index 0000000000..3d210e6e10 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/turn.py @@ -0,0 +1,117 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the Turn ability. +""" +from __future__ import annotations +import typing + +from math import degrees + +from ......nyan.nyan_structs import MemberOperator, MemberSpecialValue +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieUnitObject + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def turn_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + diff: ConverterObject = None +) -> list[ForwardRef]: + """ + Creates a patch for the Turn ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if diff: + diff_turn_speed = diff["turn_speed"] + if isinstance(diff_turn_speed, NoDiffMember): + return patches + + diff_turn_speed_value = diff_turn_speed.value + + else: + return patches + + patch_target_ref = f"{game_entity_name}.Turn" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}TurnWrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}Turn" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) + + # Speed + turn_speed_unmodified = diff_turn_speed_value + turn_speed = MemberSpecialValue.NYAN_INF + # Ships/Trebuchets turn slower + if turn_speed_unmodified > 0: + turn_yaw = diff["max_yaw_per_sec_moving"].value + turn_speed = degrees(turn_yaw) + + nyan_patch_raw_api_object.add_raw_patch_member("turn_speed", + turn_speed, + "engine.ability.type.Turn", + MemberOperator.ASSIGN) + + 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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability/util.py b/openage/convert/processor/conversion/aoc/upgrade_ability/util.py new file mode 100644 index 0000000000..6116f4718a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_ability/util.py @@ -0,0 +1,358 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Helper functions for AoC ability upgrades. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, \ + GenieVariantGroup +from .....entity_object.conversion.combined_sound import CombinedSound +from .....entity_object.conversion.combined_sprite import CombinedSprite +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup + from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def create_animation( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + animation_id: int, + container_obj_ref: str, + animation_name: str, + filename_prefix: str +) -> ForwardRef: + """ + Generates an animation for an ability. + """ + dataset = converter_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + if isinstance(converter_group, GenieVariantGroup): + group_name = str(animation_id) + + else: + tech_id = converter_group.get_id() + group_name = tech_lookup_dict[tech_id][1] + + animation_ref = f"{container_obj_ref}.{animation_name}Animation" + animation_obj_name = f"{animation_name}Animation" + animation_raw_api_object = RawAPIObject(animation_ref, animation_obj_name, + dataset.nyan_api_objects) + animation_raw_api_object.add_raw_parent("engine.util.graphics.Animation") + animation_location = ForwardRef(converter_group, container_obj_ref) + animation_raw_api_object.set_location(animation_location) + + if animation_id in dataset.combined_sprites.keys(): + animation_sprite = dataset.combined_sprites[animation_id] + + else: + if isinstance(line, GenieBuildingLineGroup): + animation_filename = (f"{filename_prefix}" + f"{name_lookup_dict[line.get_head_unit_id()][1]}_" + f"{group_name}") + + else: + animation_filename = f"{filename_prefix}{group_name}" + + animation_sprite = CombinedSprite(animation_id, + animation_filename, + dataset) + dataset.combined_sprites.update({animation_sprite.get_id(): animation_sprite}) + + animation_sprite.add_reference(animation_raw_api_object) + + animation_raw_api_object.add_raw_member("sprite", animation_sprite, + "engine.util.graphics.Animation") + + converter_group.add_raw_api_object(animation_raw_api_object) + + animation_forward_ref = ForwardRef(converter_group, animation_ref) + + return animation_forward_ref + + +def create_sound( + converter_group: ConverterObjectGroup, + sound_id: int, + container_obj_ref: str, + sound_name: str, + filename_prefix: str +) -> ForwardRef: + """ + Generates a sound for an ability. + """ + dataset = converter_group.data + + sound_ref = f"{container_obj_ref}.{sound_name}Sound" + sound_obj_name = f"{sound_name}Sound" + sound_raw_api_object = RawAPIObject(sound_ref, sound_obj_name, + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(converter_group, container_obj_ref) + sound_raw_api_object.set_location(sound_location) + + # Search for the sound if it exists + sounds_set = [] + + genie_sound = dataset.genie_sounds[sound_id] + file_ids = genie_sound.get_sounds(civ_id=-1) + + for file_id in file_ids: + if file_id in dataset.combined_sounds: + sound = dataset.combined_sounds[file_id] + + else: + sound_filename = f"{filename_prefix}sound_{str(file_id)}" + + sound = CombinedSound(sound_id, + file_id, + sound_filename, + dataset) + dataset.combined_sounds.update({file_id: sound}) + + sound.add_reference(sound_raw_api_object) + sounds_set.append(sound) + + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + sounds_set, + "engine.util.sound.Sound") + + converter_group.add_raw_api_object(sound_raw_api_object) + + sound_forward_ref = ForwardRef(converter_group, sound_ref) + + return sound_forward_ref + + +def create_language_strings( + converter_group: ConverterObjectGroup, + string_id: int, + obj_ref: str, + obj_name_prefix: str +) -> list[ForwardRef]: + """ + Generates a language string for an ability. + """ + dataset = converter_group.data + string_resources = dataset.strings.get_tables() + + string_objs = [] + for language, strings in string_resources.items(): + if string_id in strings.keys(): + string_name = f"{obj_name_prefix}String" + string_ref = f"{obj_ref}.{string_name}" + string_raw_api_object = RawAPIObject(string_ref, string_name, + dataset.nyan_api_objects) + string_raw_api_object.add_raw_parent("engine.util.language.LanguageTextPair") + string_location = ForwardRef(converter_group, obj_ref) + string_raw_api_object.set_location(string_location) + + # Language identifier + lang_forward_ref = dataset.pregen_nyan_objects[ + f"util.language.{language}" + ].get_nyan_object() + string_raw_api_object.add_raw_member("language", + lang_forward_ref, + "engine.util.language.LanguageTextPair") + + # String + string_raw_api_object.add_raw_member("string", + strings[string_id], + "engine.util.language.LanguageTextPair") + + converter_group.add_raw_api_object(string_raw_api_object) + string_forward_ref = ForwardRef(converter_group, string_ref) + string_objs.append(string_forward_ref) + + return string_objs + + +def create_animation_patch( + converter_group: ConverterObjectGroup, + line: ConverterObjectGroup, + ability_ref: str, + patch_name_prefix: str, + container_obj_ref: str, + animation_name_prefix: str, + filename_prefix: str, + animation_ids: list[int] +) -> tuple[RawAPIObject, ForwardRef]: + """ + Create a patch for the Animated property of an ability. + + :param converter_group: Converter group for storing the patch. + :param line: Line that has the ability. + :param ability_ref: Reference of the ability that has the Animated property. + :param patch_name_prefix: Prefix to the name of the patch. + :param container_obj_ref: Reference of the API object that should contain the + patch as a nested object. + :param animation_name_prefix: Prefix to the name of the animation. + :param filename_prefix: Prefix to the filename of the animation. + :param animation_ids: IDs of the animations to patch in. + :return: A 2-tuple containing the wrapper RawAPIObject and its ForwardRef. + """ + dataset = converter_group.data + + patch_target_ref = f"{ability_ref}.Animated" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"{patch_name_prefix}AnimationWrapper" + 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") + wrapper_location = ForwardRef(converter_group, container_obj_ref) + wrapper_raw_api_object.set_location(wrapper_location) + + # Nyan patch + nyan_patch_name = f"{patch_name_prefix}Animation" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) + + animations: list[ForwardRef] = [] + for idx, anim_id in enumerate(animation_ids): + if anim_id < 0: + continue + + if len(animation_ids) == 1: + # don't append index if there is only one animation + anim_obj_name = animation_name_prefix + + else: + anim_obj_name = f"{animation_name_prefix}{idx}" + + anim_forward_ref = create_animation( + converter_group, + line, + anim_id, + nyan_patch_ref, + anim_obj_name, + filename_prefix + ) + animations.append(anim_forward_ref) + + nyan_patch_raw_api_object.add_raw_patch_member("animations", + animations, + "engine.ability.property.type.Animated", + MemberOperator.ASSIGN) + + 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) + + return wrapper_raw_api_object, wrapper_forward_ref + + +def create_command_sound_patch( + converter_group: ConverterObjectGroup, + line: ConverterObjectGroup, + ability_ref: str, + patch_name_prefix: str, + container_obj_ref: str, + sound_name_prefix: str, + filename_prefix: str, + sound_ids: list[int] +) -> tuple[RawAPIObject, ForwardRef]: + """ + Create a patch for the CommandSound property of an ability. + + :param converter_group: Converter group for storing the patch. + :param line: Line that has the ability. + :param ability_ref: Reference of the ability that has the CommandSound property. + :param patch_name_prefix: Prefix to the name of the patch. + :param container_obj_ref: Reference of the API object that should contain the + patch as a nested object. + :param sound_name_prefix: Prefix to the name of the sound. + :param filename_prefix: Prefix to the filename of the sound. + :param sound_ids: IDs of the sounds to patch in. + :return: A 2-tuple containing the wrapper RawAPIObject and its ForwardRef. + """ + dataset = converter_group.data + + patch_target_ref = f"{ability_ref}.CommandSound" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"{patch_name_prefix}CommandSoundWrapper" + 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") + wrapper_location = ForwardRef(converter_group, container_obj_ref) + wrapper_raw_api_object.set_location(wrapper_location) + + # Nyan patch + nyan_patch_name = f"{patch_name_prefix}CommandSound" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) + + sounds: list[ForwardRef] = [] + for idx, sound_id in enumerate(sound_ids): + if sound_id < 0: + continue + + if len(sound_ids) == 1: + # don't append index if there is only one sound + sound_obj_name = sound_name_prefix + + else: + sound_obj_name = f"{sound_name_prefix}{idx}" + + sound_forward_ref = create_sound( + converter_group, + sound_id, + nyan_patch_ref, + sound_obj_name, + filename_prefix + ) + sounds.append(sound_forward_ref) + + nyan_patch_raw_api_object.add_raw_patch_member("sounds", + sounds, + "engine.ability.property.type.CommandSound", + MemberOperator.ASSIGN) + + 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) + + return wrapper_raw_api_object, wrapper_forward_ref diff --git a/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py b/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py index 7fd1cf0e36..b52a1ed960 100644 --- a/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/upgrade_ability_subprocessor.py @@ -1,37 +1,25 @@ # 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 -# -# TODO: -# pylint: disable=unused-argument,line-too-long,too-many-positional-arguments """ Creates upgrade patches for abilities. """ -from __future__ import annotations -import typing - - -from math import degrees - -from .....nyan.nyan_structs import MemberOperator, MemberSpecialValue -from ....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup -from ....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, \ - GenieVariantGroup, GenieUnitLineGroup -from ....entity_object.conversion.combined_sound import CombinedSound -from ....entity_object.conversion.combined_sprite import CombinedSprite -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ....value_object.read.value_members import NoDiffMember -from .upgrade_effect_subprocessor import AoCUpgradeEffectSubprocessor - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObject, \ - ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup, \ - GenieUnitObject +from .upgrade_ability.apply_continuous_effect import apply_continuous_effect_ability +from .upgrade_ability.apply_discrete_effect import apply_discrete_effect_ability +from .upgrade_ability.attribute_change_tracker import attribute_change_tracker_ability +from .upgrade_ability.death import death_ability +from .upgrade_ability.despawn import despawn_ability +from .upgrade_ability.idle import idle_ability +from .upgrade_ability.line_of_sight import line_of_sight_ability +from .upgrade_ability.live import live_ability +from .upgrade_ability.move import move_ability +from .upgrade_ability.named import named_ability +from .upgrade_ability.resistance import resistance_ability +from .upgrade_ability.selectable import selectable_ability +from .upgrade_ability.shoot_projectile import shoot_projectile_ability +from .upgrade_ability.turn import turn_ability + +from .upgrade_ability.util import create_animation, create_sound, create_language_strings, \ + create_animation_patch, create_command_sound_patch class AoCUpgradeAbilitySubprocessor: @@ -39,2216 +27,23 @@ class AoCUpgradeAbilitySubprocessor: Creates raw API objects for ability upgrade effects in AoC. """ - @staticmethod - def apply_continuous_effect_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - command_id: int, - ranged: bool = False, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the ApplyContinuousEffect ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - ability_name = command_lookup_dict[command_id][0] - - data_changed = False - diff_animation = diff["attack_sprite_id"] - diff_comm_sound = diff["command_sound_id"] - diff_frame_delay = diff["frame_delay"] - if any(not isinstance(value, NoDiffMember) for value in (diff_frame_delay)): - data_changed = True - - # Command types Heal, Construct, Repair are not upgraded by lines - - 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 - )): - 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 - - # Nyan patch - patch_target_ref = f"{game_entity_name}.{ability_name}" - nyan_patch_name = f"Change{game_entity_name}{ability_name}" - wrapper, anim_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_", - [diff_animation_id] - ) - patches.append(anim_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if not isinstance(diff_comm_sound, NoDiffMember): - diff_comm_sound_id = diff_comm_sound.value - - # Nyan patch - patch_target_ref = f"{game_entity_name}.{ability_name}" - nyan_patch_name = f"Change{game_entity_name}{ability_name}" - wrapper, sound_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_command_sound_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_", - [diff_comm_sound_id] - ) - patches.append(sound_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if data_changed: - patch_target_ref = f"{game_entity_name}.{ability_name}" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}{ability_name}Wrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_frame_delay, NoDiffMember): - if not isinstance(diff_animation, NoDiffMember): - attack_graphic_id = diff_animation.value - - else: - attack_graphic_id = diff_animation.ref.value - - attack_graphic = dataset.genie_graphics[attack_graphic_id] - frame_rate = attack_graphic.get_frame_rate() - frame_delay = diff_frame_delay.value - application_delay = frame_rate * frame_delay - - nyan_patch_raw_api_object.add_raw_patch_member("application_delay", - application_delay, - "engine.ability.type.ApplyContinuousEffect", - MemberOperator.ASSIGN) - - 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) - - return patches - - @staticmethod - def apply_discrete_effect_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - command_id: int, - ranged: bool = False, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the ApplyDiscreteEffect ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - ability_name = command_lookup_dict[command_id][0] - - data_changed = False - diff_animation = diff["attack_sprite_id"] - diff_comm_sound = diff["command_sound_id"] - diff_reload_time = diff["attack_speed"] - diff_frame_delay = diff["frame_delay"] - if any(not isinstance(value, NoDiffMember) for value in (diff_reload_time, - diff_frame_delay)): - data_changed = True - - 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 - )): - 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 - - # Nyan patch - patch_target_ref = f"{game_entity_name}.{ability_name}" - nyan_patch_name = f"Change{game_entity_name}{ability_name}" - wrapper, anim_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_", - [diff_animation_id] - ) - patches.append(anim_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if not isinstance(diff_comm_sound, NoDiffMember): - diff_comm_sound_id = diff_comm_sound.value - - # Nyan patch - patch_target_ref = f"{game_entity_name}.{ability_name}" - nyan_patch_name = f"Change{game_entity_name}{ability_name}" - wrapper, sound_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_command_sound_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_", - [diff_comm_sound_id] - ) - patches.append(sound_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if data_changed: - patch_target_ref = f"{game_entity_name}.{ability_name}" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}{ability_name}Wrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_reload_time, NoDiffMember): - reload_time = diff_reload_time.value - - nyan_patch_raw_api_object.add_raw_patch_member("reload_time", - reload_time, - "engine.ability.type.ApplyDiscreteEffect", - MemberOperator.ADD) - - if not isinstance(diff_frame_delay, NoDiffMember): - if not isinstance(diff_animation, NoDiffMember): - attack_graphic_id = diff_animation.value - - else: - attack_graphic_id = diff_animation.ref.value - - attack_graphic = dataset.genie_graphics[attack_graphic_id] - frame_rate = attack_graphic.get_frame_rate() - frame_delay = diff_frame_delay.value - application_delay = frame_rate * frame_delay - - nyan_patch_raw_api_object.add_raw_patch_member("application_delay", - application_delay, - "engine.ability.type.ApplyDiscreteEffect", - MemberOperator.ASSIGN) - - 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) - - # Seperate because effects get their own wrappers from the subprocessor - data_changed = False - diff_attacks = None - if not data_changed and command_id == 7: - diff_attacks = diff["attacks"] - if not isinstance(diff_attacks, NoDiffMember): - data_changed = True - - if data_changed: - patch_target_ref = f"{game_entity_name}.{ability_name}" - if command_id == 7 and not isinstance(diff_attacks, NoDiffMember): - patches.extend(AoCUpgradeEffectSubprocessor.get_attack_effects(converter_group, - line, diff, - patch_target_ref)) - - return patches - - @staticmethod - def attribute_change_tracker_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the AttributeChangeTracker ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if diff: - diff_damage_graphics = diff["damage_graphics"] - if isinstance(diff_damage_graphics, NoDiffMember): - return patches - - diff_damage_animations = diff_damage_graphics.value - - else: - return patches - - percentage = 0 - for diff_damage_animation in diff_damage_animations: - if isinstance(diff_damage_animation, NoDiffMember) or\ - isinstance(diff_damage_animation["graphic_id"], NoDiffMember): - continue - - # This should be a NoDiffMember - percentage = diff_damage_animation["damage_percent"].ref.value - - patch_target_ref = (f"{game_entity_name}.AttributeChangeTracker." - f"ChangeProgress{percentage}.AnimationOverlay") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}DamageGraphic{percentage}Wrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}DamageGraphic{str(percentage)}" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) - - animations_set = [] - diff_animation_id = diff_damage_animation["graphic_id"].value - if diff_animation_id > -1: - # Patch the new animation in - animation_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation( - converter_group, - line, - diff_animation_id, - nyan_patch_ref, - "Idle", - f"idle_damage_override_{percentage}_" - ) - animations_set.append(animation_forward_ref) - - nyan_patch_raw_api_object.add_raw_patch_member("overlays", - animations_set, - "engine.util.progress.property.type.AnimationOverlay", - MemberOperator.ASSIGN) - - 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) - - return patches - - @staticmethod - def death_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the Death ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if diff: - diff_animation = diff["dying_graphic"] - if isinstance(diff_animation, NoDiffMember): - return patches - - # TODO: If the head unit has an invalid -1 graphic, it doesnt get the Animated - # property for the ability in the ability subprocessor, so - # we can't patch it here. - # - # We have to find a solution for this, e.g. patch in the Animated ability - # here or in the ability subprocessor. - if line.get_head_unit()["dying_graphic"].value == -1: - return patches - - diff_animation_id = diff_animation.value - - else: - return patches - - patch_target_ref = f"{game_entity_name}.Death" - nyan_patch_name = f"Change{game_entity_name}Death" - - # Nyan patch - wrapper, anim_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - "Death", - "death_", - [diff_animation_id] - ) - patches.append(anim_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - return patches - - @staticmethod - def despawn_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the Despawn ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if diff: - diff_dead_unit = diff["dead_unit_id"] - if isinstance(diff_dead_unit, NoDiffMember): - return patches - - diff_animation_id = dataset.genie_units[diff_dead_unit.value]["idle_graphic0"].value - - # TODO: If the head unit has an invalid -1 graphic, it doesnt get the Animated - # property for the ability in the ability subprocessor, so - # we can't patch it here. - # - # We have to find a solution for this, e.g. patch in the Animated ability - # here or in the ability subprocessor. - dead_unit_id = line.get_head_unit()["dead_unit_id"].value - if dead_unit_id == -1: - return patches - - dead_unit = dataset.genie_units[dead_unit_id] - dead_unit_animation_id = dead_unit["idle_graphic0"].value - if dead_unit_animation_id == -1: - return patches - - else: - return patches - - patch_target_ref = f"{game_entity_name}.Despawn" - nyan_patch_name = f"Change{game_entity_name}Despawn" - - # Nyan patch - wrapper, anim_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - "Despawn", - "despawn_", - [diff_animation_id] - ) - patches.append(anim_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - return patches - - @staticmethod - def idle_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the Idle ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if diff: - diff_animation = diff["idle_graphic0"] - if isinstance(diff_animation, NoDiffMember): - return patches - - # TODO: If the head unit has an invalid -1 graphic, it doesnt get the Animated - # property for the ability in the ability subprocessor, so - # we can't patch it here. - # - # We have to find a solution for this, e.g. patch in the Animated ability - # here or in the ability subprocessor. - if line.get_head_unit()["idle_graphic0"].value == -1: - return patches - - diff_animation_id = diff_animation.value - - else: - return patches - - patch_target_ref = f"{game_entity_name}.Idle" - nyan_patch_name = f"Change{game_entity_name}Idle" - - # Nyan patch - wrapper, anim_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - "Idle", - "idle_", - [diff_animation_id] - ) - patches.append(anim_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - return patches - - @staticmethod - def live_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the Live ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if diff: - diff_hp = diff["hit_points"] - if isinstance(diff_hp, NoDiffMember): - return patches - - diff_hp_value = diff_hp.value - - else: - return patches - - patch_target_ref = f"{game_entity_name}.Live.Health" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}HealthWrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}Health" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) - - # HP max value - nyan_patch_raw_api_object.add_raw_patch_member("max_value", - diff_hp_value, - "engine.util.attribute.AttributeSetting", - MemberOperator.ADD) - - # HP starting value - nyan_patch_raw_api_object.add_raw_patch_member("starting_value", - diff_hp_value, - "engine.util.attribute.AttributeSetting", - 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) - - return patches - - @staticmethod - def los_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the LineOfSight ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if diff: - diff_line_of_sight = diff["line_of_sight"] - if isinstance(diff_line_of_sight, NoDiffMember): - return patches - - diff_los_range = diff_line_of_sight.value - - else: - return patches - - patch_target_ref = f"{game_entity_name}.LineOfSight" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}LineOfSightWrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}LineOfSight" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) - - # Line of Sight - nyan_patch_raw_api_object.add_raw_patch_member("range", - diff_los_range, - "engine.ability.type.LineOfSight", - 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) - - return patches - - @staticmethod - def move_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the Move ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - data_changed = False - diff_move_animation = diff["move_graphics"] - diff_comm_sound = diff["command_sound_id"] - diff_move_speed = diff["speed"] - if any(not isinstance(value, NoDiffMember) for value in (diff_move_speed,)): - data_changed = True - - if not isinstance(diff_move_animation, NoDiffMember): - diff_animation_id = diff_move_animation.value - - # Nyan patch - patch_target_ref = f"{game_entity_name}.Move" - nyan_patch_name = f"Change{game_entity_name}Move" - wrapper, anim_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - "Move", - "move_", - [diff_animation_id] - ) - patches.append(anim_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if not isinstance(diff_comm_sound, NoDiffMember): - diff_comm_sound_id = diff_comm_sound.value - - # Nyan patch - patch_target_ref = f"{game_entity_name}.Move" - nyan_patch_name = f"Change{game_entity_name}Move" - wrapper, sound_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_command_sound_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - "Move", - "move_", - [diff_comm_sound_id] - ) - patches.append(sound_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if data_changed: - patch_target_ref = f"{game_entity_name}.Move" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}MoveWrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}Move" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_move_speed, NoDiffMember): - diff_speed_value = diff_move_speed.value - - nyan_patch_raw_api_object.add_raw_patch_member("speed", - diff_speed_value, - "engine.ability.type.Move", - 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) - - return patches - - @staticmethod - def named_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the Named ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - group_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if isinstance(converter_group, GenieTechEffectBundleGroup): - obj_prefix = tech_lookup_dict[group_id][0] - - else: - obj_prefix = game_entity_name - - diff_name = diff["language_dll_name"] - if not isinstance(diff_name, NoDiffMember): - patch_target_ref = f"{game_entity_name}.Named.{game_entity_name}Name" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}NameWrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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[group_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}Name" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) - - name_string_id = diff_name.value - translations = AoCUpgradeAbilitySubprocessor.create_language_strings( - converter_group, - name_string_id, - nyan_patch_ref, - f"{obj_prefix}Name" - ) - nyan_patch_raw_api_object.add_raw_patch_member("translations", - translations, - "engine.util.language.translated.type.TranslatedString", - MemberOperator.ASSIGN) - - 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) - - return patches - - @staticmethod - def resistance_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the Resistance ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - diff_armors = diff["armors"] - if not isinstance(diff_armors, NoDiffMember): - patch_target_ref = f"{game_entity_name}.Resistance" - patches.extend(AoCUpgradeEffectSubprocessor.get_attack_resistances(converter_group, - line, diff, - patch_target_ref)) - - # TODO: Other resistance types - - return patches - - @staticmethod - def selectable_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the Selectable ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - # First patch: Sound for the SelectableSelf ability - changed = False - if diff: - diff_selection_sound = diff["selection_sound_id"] - if not isinstance(diff_selection_sound, NoDiffMember): - changed = True - - if isinstance(line, GenieUnitLineGroup): - ability_name = "SelectableSelf" - - else: - ability_name = "Selectable" - - if changed: - patch_target_ref = f"{game_entity_name}.{ability_name}" - nyan_patch_name = f"Change{game_entity_name}{ability_name}" - - # Change sound - diff_selection_sound_id = diff_selection_sound.value - # Nyan patch - wrapper, sound_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_command_sound_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - ability_name, - "select_", - [diff_selection_sound_id] - ) - patches.append(sound_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - # Second patch: Selection box - changed = False - if diff: - diff_radius_x = diff["selection_shape_x"] - diff_radius_y = diff["selection_shape_y"] - if any(not isinstance(value, NoDiffMember) for value in (diff_radius_x, - diff_radius_y)): - changed = True - - if changed: - patch_target_ref = f"{game_entity_name}.{ability_name}.Rectangle" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}{ability_name}RectangleWrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}Rectangle" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_radius_x, NoDiffMember): - diff_width_value = diff_radius_x.value - - nyan_patch_raw_api_object.add_raw_patch_member("width", - diff_width_value, - "engine.util.selection_box.type.Rectangle", - MemberOperator.ADD) - - if not isinstance(diff_radius_y, NoDiffMember): - diff_height_value = diff_radius_y.value - - nyan_patch_raw_api_object.add_raw_patch_member("height", - diff_height_value, - "engine.util.selection_box.type.Rectangle", - 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) - - return patches - - @staticmethod - def shoot_projectile_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - upgrade_source: GenieUnitObject, - upgrade_target: GenieUnitObject, - command_id: int, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - 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 - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - ability_name = command_lookup_dict[command_id][0] - - data_changed = False - - 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_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 - - # Nyan patch - patch_target_ref = f"{game_entity_name}.{ability_name}" - nyan_patch_name = f"Change{game_entity_name}{ability_name}" - wrapper, anim_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_", - [diff_animation_id] - ) - patches.append(anim_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if not isinstance(diff_comm_sound, NoDiffMember): - diff_comm_sound_id = diff_comm_sound.value - - # Nyan patch - patch_target_ref = f"{game_entity_name}.{ability_name}" - nyan_patch_name = f"Change{game_entity_name}{ability_name}" - wrapper, sound_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_command_sound_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_", - [diff_comm_sound_id] - ) - patches.append(sound_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if data_changed: - patch_target_ref = f"{game_entity_name}.{ability_name}" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}{ability_name}Wrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_projectiles, NoDiffMember): - min_projectiles = diff_min_projectiles.value - source_min_count = upgrade_source["projectile_min_count"].value - source_max_count = upgrade_source["projectile_max_count"].value - target_min_count = upgrade_target["projectile_min_count"].value - target_max_count = upgrade_target["projectile_max_count"].value - - # Account for a special case where the number of projectiles are 0 - # in the .dat, but the game still counts this as 1 when a projectile - # is defined. - if source_min_count == 0 and source_max_count == 0: - min_projectiles -= 1 - - if target_min_count == 0 and target_max_count == 0: - min_projectiles += 1 - - if min_projectiles != 0: - nyan_patch_raw_api_object.add_raw_patch_member("min_projectiles", - min_projectiles, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_max_projectiles, NoDiffMember): - max_projectiles = diff_max_projectiles.value - source_min_count = upgrade_source["projectile_min_count"].value - source_max_count = upgrade_source["projectile_max_count"].value - target_min_count = upgrade_target["projectile_min_count"].value - target_max_count = upgrade_target["projectile_max_count"].value - - # Account for a special case where the number of projectiles are 0 - # in the .dat, but the game still counts this as 1 when a projectile - # is defined. - if source_min_count == 0 and source_max_count == 0: - max_projectiles -= 1 - - if target_min_count == 0 and target_max_count == 0: - max_projectiles += 1 - - if max_projectiles != 0: - nyan_patch_raw_api_object.add_raw_patch_member("max_projectiles", - max_projectiles, - "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", - reload_time, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_spawn_delay, NoDiffMember): - if not isinstance(diff_animation, NoDiffMember): - attack_graphic_id = diff_animation.value - - else: - attack_graphic_id = diff_animation.ref.value - - attack_graphic = dataset.genie_graphics[attack_graphic_id] - frame_rate = attack_graphic.get_frame_rate() - frame_delay = diff_spawn_delay.value - spawn_delay = frame_rate * frame_delay - - nyan_patch_raw_api_object.add_raw_patch_member("spawn_delay", - spawn_delay, - "engine.ability.type.ShootProjectile", - MemberOperator.ASSIGN) - - if not isinstance(diff_spawn_area_offsets, NoDiffMember): - diff_spawn_area_x = diff_spawn_area_offsets[0] - diff_spawn_area_y = diff_spawn_area_offsets[1] - diff_spawn_area_z = diff_spawn_area_offsets[2] - - if not isinstance(diff_spawn_area_x, NoDiffMember): - spawn_area_x = diff_spawn_area_x.value - - nyan_patch_raw_api_object.add_raw_patch_member("spawning_area_offset_x", - spawn_area_x, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_spawn_area_y, NoDiffMember): - spawn_area_y = diff_spawn_area_y.value - - nyan_patch_raw_api_object.add_raw_patch_member("spawning_area_offset_y", - spawn_area_y, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_spawn_area_z, NoDiffMember): - spawn_area_z = diff_spawn_area_z.value - - nyan_patch_raw_api_object.add_raw_patch_member("spawning_area_offset_z", - spawn_area_z, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_spawn_area_width, NoDiffMember): - spawn_area_width = diff_spawn_area_width.value - - nyan_patch_raw_api_object.add_raw_patch_member("spawning_area_width", - spawn_area_width, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_spawn_area_height, NoDiffMember): - spawn_area_height = diff_spawn_area_height.value - - nyan_patch_raw_api_object.add_raw_patch_member("spawning_area_height", - spawn_area_height, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_spawn_area_randomness, NoDiffMember): - spawn_area_randomness = diff_spawn_area_randomness.value - - nyan_patch_raw_api_object.add_raw_patch_member("spawning_area_randomness", - spawn_area_randomness, - "engine.ability.type.ShootProjectile", - 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) - - return patches - - @staticmethod - def turn_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - diff: ConverterObject = None - ) -> list[ForwardRef]: - """ - Creates a patch for the Turn ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if diff: - diff_turn_speed = diff["turn_speed"] - if isinstance(diff_turn_speed, NoDiffMember): - return patches - - diff_turn_speed_value = diff_turn_speed.value - - else: - return patches - - patch_target_ref = f"{game_entity_name}.Turn" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}TurnWrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}Turn" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) - - # Speed - turn_speed_unmodified = diff_turn_speed_value - turn_speed = MemberSpecialValue.NYAN_INF - # Ships/Trebuchets turn slower - if turn_speed_unmodified > 0: - turn_yaw = diff["max_yaw_per_sec_moving"].value - turn_speed = degrees(turn_yaw) - - nyan_patch_raw_api_object.add_raw_patch_member("turn_speed", - turn_speed, - "engine.ability.type.Turn", - MemberOperator.ASSIGN) - - 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) - - return patches - - @staticmethod - def create_animation( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - animation_id: int, - container_obj_ref: str, - animation_name: str, - filename_prefix: str - ) -> ForwardRef: - """ - Generates an animation for an ability. - """ - dataset = converter_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - if isinstance(converter_group, GenieVariantGroup): - group_name = str(animation_id) - - else: - tech_id = converter_group.get_id() - group_name = tech_lookup_dict[tech_id][1] - - animation_ref = f"{container_obj_ref}.{animation_name}Animation" - animation_obj_name = f"{animation_name}Animation" - animation_raw_api_object = RawAPIObject(animation_ref, animation_obj_name, - dataset.nyan_api_objects) - animation_raw_api_object.add_raw_parent("engine.util.graphics.Animation") - animation_location = ForwardRef(converter_group, container_obj_ref) - animation_raw_api_object.set_location(animation_location) - - if animation_id in dataset.combined_sprites.keys(): - animation_sprite = dataset.combined_sprites[animation_id] - - else: - if isinstance(line, GenieBuildingLineGroup): - animation_filename = (f"{filename_prefix}" - f"{name_lookup_dict[line.get_head_unit_id()][1]}_" - f"{group_name}") - - else: - animation_filename = f"{filename_prefix}{group_name}" - - animation_sprite = CombinedSprite(animation_id, - animation_filename, - dataset) - dataset.combined_sprites.update({animation_sprite.get_id(): animation_sprite}) - - animation_sprite.add_reference(animation_raw_api_object) - - animation_raw_api_object.add_raw_member("sprite", animation_sprite, - "engine.util.graphics.Animation") - - converter_group.add_raw_api_object(animation_raw_api_object) - - animation_forward_ref = ForwardRef(converter_group, animation_ref) - - return animation_forward_ref - - @staticmethod - def create_sound( - converter_group: ConverterObjectGroup, - sound_id: int, - container_obj_ref: str, - sound_name: str, - filename_prefix: str - ) -> ForwardRef: - """ - Generates a sound for an ability. - """ - dataset = converter_group.data - - sound_ref = f"{container_obj_ref}.{sound_name}Sound" - sound_obj_name = f"{sound_name}Sound" - sound_raw_api_object = RawAPIObject(sound_ref, sound_obj_name, - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(converter_group, container_obj_ref) - sound_raw_api_object.set_location(sound_location) - - # Search for the sound if it exists - sounds_set = [] - - genie_sound = dataset.genie_sounds[sound_id] - file_ids = genie_sound.get_sounds(civ_id=-1) - - for file_id in file_ids: - if file_id in dataset.combined_sounds: - sound = dataset.combined_sounds[file_id] - - else: - sound_filename = f"{filename_prefix}sound_{str(file_id)}" - - sound = CombinedSound(sound_id, - file_id, - sound_filename, - dataset) - dataset.combined_sounds.update({file_id: sound}) - - sound.add_reference(sound_raw_api_object) - sounds_set.append(sound) - - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - sounds_set, - "engine.util.sound.Sound") - - converter_group.add_raw_api_object(sound_raw_api_object) - - sound_forward_ref = ForwardRef(converter_group, sound_ref) - - return sound_forward_ref - - @staticmethod - def create_language_strings( - converter_group: ConverterObjectGroup, - string_id: int, - obj_ref: str, - obj_name_prefix: str - ) -> list[ForwardRef]: - """ - Generates a language string for an ability. - """ - dataset = converter_group.data - string_resources = dataset.strings.get_tables() - - string_objs = [] - for language, strings in string_resources.items(): - if string_id in strings.keys(): - string_name = f"{obj_name_prefix}String" - string_ref = f"{obj_ref}.{string_name}" - string_raw_api_object = RawAPIObject(string_ref, string_name, - dataset.nyan_api_objects) - string_raw_api_object.add_raw_parent("engine.util.language.LanguageTextPair") - string_location = ForwardRef(converter_group, obj_ref) - string_raw_api_object.set_location(string_location) - - # Language identifier - lang_forward_ref = dataset.pregen_nyan_objects[f"util.language.{language}"].get_nyan_object( - ) - string_raw_api_object.add_raw_member("language", - lang_forward_ref, - "engine.util.language.LanguageTextPair") - - # String - string_raw_api_object.add_raw_member("string", - strings[string_id], - "engine.util.language.LanguageTextPair") - - converter_group.add_raw_api_object(string_raw_api_object) - string_forward_ref = ForwardRef(converter_group, string_ref) - string_objs.append(string_forward_ref) - - return string_objs - - @staticmethod - def create_animation_patch( - converter_group: ConverterObjectGroup, - line: ConverterObjectGroup, - ability_ref: str, - patch_name_prefix: str, - container_obj_ref: str, - animation_name_prefix: str, - filename_prefix: str, - animation_ids: list[int] - ) -> tuple[RawAPIObject, ForwardRef]: - """ - Create a patch for the Animated property of an ability. - - :param converter_group: Converter group for storing the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param ability_ref: Reference of the ability that has the Animated property. - :type ability_ref: str - :param patch_name_prefix: Prefix to the name of the patch. - :type patch_name_prefix: str - :param container_obj_ref: Reference of the API object that should contain the - patch as a nested object. - :type container_obj_ref: str - :param animation_name_prefix: Prefix to the name of the animation. - :type animation_name_prefix: str - :param filename_prefix: Prefix to the filename of the animation. - :type filename_prefix: str - :param animation_ids: IDs of the animations to patch in. - :type animation_ids: list[int] - """ - dataset = converter_group.data - - patch_target_ref = f"{ability_ref}.Animated" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"{patch_name_prefix}AnimationWrapper" - 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") - wrapper_location = ForwardRef(converter_group, container_obj_ref) - wrapper_raw_api_object.set_location(wrapper_location) - - # Nyan patch - nyan_patch_name = f"{patch_name_prefix}Animation" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) - - animations: list[ForwardRef] = [] - for idx, anim_id in enumerate(animation_ids): - if anim_id < 0: - continue - - if len(animation_ids) == 1: - # don't append index if there is only one animation - anim_obj_name = animation_name_prefix - - else: - anim_obj_name = f"{animation_name_prefix}{idx}" - - anim_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation( - converter_group, - line, - anim_id, - nyan_patch_ref, - anim_obj_name, - filename_prefix - ) - animations.append(anim_forward_ref) - - nyan_patch_raw_api_object.add_raw_patch_member("animations", - animations, - "engine.ability.property.type.Animated", - MemberOperator.ASSIGN) - - 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) - - return wrapper_raw_api_object, wrapper_forward_ref - - @staticmethod - def create_command_sound_patch( - converter_group: ConverterObjectGroup, - line: ConverterObjectGroup, - ability_ref: str, - patch_name_prefix: str, - container_obj_ref: str, - sound_name_prefix: str, - filename_prefix: str, - sound_ids: list[int] - ) -> tuple[RawAPIObject, ForwardRef]: - """ - Create a patch for the CommandSound property of an ability. - - :param converter_group: Converter group for storing the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - """ - dataset = converter_group.data - - patch_target_ref = f"{ability_ref}.CommandSound" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"{patch_name_prefix}CommandSoundWrapper" - 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") - wrapper_location = ForwardRef(converter_group, container_obj_ref) - wrapper_raw_api_object.set_location(wrapper_location) - - # Nyan patch - nyan_patch_name = f"{patch_name_prefix}CommandSound" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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) - - sounds: list[ForwardRef] = [] - for idx, sound_id in enumerate(sound_ids): - if sound_id < 0: - continue - - if len(sound_ids) == 1: - # don't append index if there is only one sound - sound_obj_name = sound_name_prefix - - else: - sound_obj_name = f"{sound_name_prefix}{idx}" - - sound_forward_ref = AoCUpgradeAbilitySubprocessor.create_sound( - converter_group, - sound_id, - nyan_patch_ref, - sound_obj_name, - filename_prefix - ) - sounds.append(sound_forward_ref) - - nyan_patch_raw_api_object.add_raw_patch_member("sounds", - sounds, - "engine.ability.property.type.CommandSound", - MemberOperator.ASSIGN) - - 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) - - return wrapper_raw_api_object, wrapper_forward_ref + apply_continuous_effect_ability = staticmethod(apply_continuous_effect_ability) + apply_discrete_effect_ability = staticmethod(apply_discrete_effect_ability) + attribute_change_tracker_ability = staticmethod(attribute_change_tracker_ability) + death_ability = staticmethod(death_ability) + despawn_ability = staticmethod(despawn_ability) + idle_ability = staticmethod(idle_ability) + los_ability = staticmethod(line_of_sight_ability) + live_ability = staticmethod(live_ability) + move_ability = staticmethod(move_ability) + named_ability = staticmethod(named_ability) + resistance_ability = staticmethod(resistance_ability) + selectable_ability = staticmethod(selectable_ability) + shoot_projectile_ability = staticmethod(shoot_projectile_ability) + turn_ability = staticmethod(turn_ability) + + create_animation = staticmethod(create_animation) + create_sound = staticmethod(create_sound) + create_language_strings = staticmethod(create_language_strings) + create_animation_patch = staticmethod(create_animation_patch) + create_command_sound_patch = staticmethod(create_command_sound_patch) diff --git a/openage/convert/processor/conversion/aoc/upgrade_effect/CMakeLists.txt b/openage/convert/processor/conversion/aoc/upgrade_effect/CMakeLists.txt new file mode 100644 index 0000000000..40b138d46a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_effect/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + attack.py +) diff --git a/openage/convert/processor/conversion/aoc/upgrade_effect/__init__.py b/openage/convert/processor/conversion/aoc/upgrade_effect/__init__.py new file mode 100644 index 0000000000..90064d419a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_effect/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create nyan patches for upgrading effects of abilities of AoC entities. +""" diff --git a/openage/convert/processor/conversion/aoc/upgrade_effect/attack.py b/openage/convert/processor/conversion/aoc/upgrade_effect/attack.py new file mode 100644 index 0000000000..cfa6fdf824 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_effect/attack.py @@ -0,0 +1,305 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Upgrades attack effects of AoC entities. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember, LeftMissingMember, \ + RightMissingMember + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject + from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +EFFECT_PARENT = "engine.effect.discrete.flat_attribute_change.FlatAttributeChange" +ATTACK_PARENT = "engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" + + +def get_attack_effects( + tech_group: GenieTechEffectBundleGroup, + line: GenieGameEntityGroup, + diff: ConverterObject, + ability_ref: str +) -> list[ForwardRef]: + """ + Upgrades effects that are used for attacking (unit command: 7) + + :param tech_group: Tech that gets the patch. + :type tech_group: ...dataformat.converter_object.ConverterObjectGroup + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param diff: A diff between two ConvertObject instances. + :type diff: ...dataformat.converter_object.ConverterObject + :param ability_ref: Reference of the ability raw API object the effects are added to. + :type ability_ref: str + :returns: The forward references for the effects. + :rtype: list + """ + head_unit_id = line.get_head_unit_id() + tech_id = tech_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + tech_name = tech_lookup_dict[tech_id][0] + + diff_attacks = diff["attacks"].value + for diff_attack in diff_attacks.values(): + if isinstance(diff_attack, NoDiffMember): + continue + + if isinstance(diff_attack, LeftMissingMember): + # Create a new attack effect, then patch it in + attack = diff_attack.ref + + armor_class = attack["type_id"].value + attack_amount = attack["amount"].value + + if armor_class == -1: + continue + + class_name = armor_lookup_dict[armor_class] + + # FlatAttributeChangeDecrease + patch_target_ref = f"{ability_ref}.Batch" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Add{class_name}AttackEffectWrapper" + wrapper_ref = f"{tech_name}.{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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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(tech_group, tech_name)) + + # Nyan patch + nyan_patch_name = f"Add{class_name}AttackEffect" + nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(tech_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) + + # New attack effect + # ============================================================================ + attack_ref = f"{nyan_patch_ref}.{class_name}" + attack_raw_api_object = RawAPIObject(attack_ref, + class_name, + dataset.nyan_api_objects) + attack_raw_api_object.add_raw_parent(ATTACK_PARENT) + attack_location = ForwardRef(tech_group, nyan_patch_ref) + attack_raw_api_object.set_location(attack_location) + + # Type + type_ref = f"util.attribute_change_type.types.{class_name}" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + attack_raw_api_object.add_raw_member("type", + change_type, + EFFECT_PARENT) + + # Min value (optional) + min_value = dataset.pregen_nyan_objects[ + "effect.discrete.flat_attribute_change.min_damage.AoE2MinChangeAmount" + ].get_nyan_object() + attack_raw_api_object.add_raw_member("min_change_value", + min_value, + EFFECT_PARENT) + + # Max value (optional; not added because there is none in AoE2) + + # Change value + # ================================================================================= + amount_name = f"{nyan_patch_ref}.{class_name}.ChangeAmount" + amount_raw_api_object = RawAPIObject( + amount_name, "ChangeAmount", dataset.nyan_api_objects) + amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") + amount_location = ForwardRef(line, attack_ref) + amount_raw_api_object.set_location(amount_location) + + attribute = dataset.pregen_nyan_objects[ + "util.attribute.types.Health" + ].get_nyan_object() + amount_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeAmount") + amount_raw_api_object.add_raw_member("amount", + attack_amount, + "engine.util.attribute.AttributeAmount") + + line.add_raw_api_object(amount_raw_api_object) + # ================================================================================= + amount_forward_ref = ForwardRef(line, amount_name) + attack_raw_api_object.add_raw_member("change_value", + amount_forward_ref, + EFFECT_PARENT) + + # Ignore protection + attack_raw_api_object.add_raw_member("ignore_protection", + [], + EFFECT_PARENT) + + # Effect is added to the line, so it can be referenced by other upgrades + line.add_raw_api_object(attack_raw_api_object) + # ============================================================================ + attack_forward_ref = ForwardRef(line, attack_ref) + nyan_patch_raw_api_object.add_raw_patch_member("effects", + [attack_forward_ref], + "engine.util.effect_batch.EffectBatch", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + tech_group.add_raw_api_object(wrapper_raw_api_object) + tech_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + elif isinstance(diff_attack, RightMissingMember): + # Patch the effect out of the ability + attack = diff_attack.ref + + armor_class = attack["type_id"].value + class_name = armor_lookup_dict[armor_class] + + patch_target_ref = f"{ability_ref}.Batch" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Remove{class_name}AttackEffectWrapper" + wrapper_ref = f"{tech_name}.{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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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(tech_group, tech_name)) + + # Nyan patch + nyan_patch_name = f"Remove{class_name}AttackEffect" + nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(tech_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) + + attack_ref = f"{ability_ref}.{class_name}" + attack_forward_ref = ForwardRef(line, attack_ref) + nyan_patch_raw_api_object.add_raw_patch_member("effects", + [attack_forward_ref], + "engine.util.effect_batch.EffectBatch", + MemberOperator.SUBTRACT) + + patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + tech_group.add_raw_api_object(wrapper_raw_api_object) + tech_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + else: + diff_armor_class = diff_attack["type_id"] + if not isinstance(diff_armor_class, NoDiffMember): + # If this happens then the attacks are out of order + # and we have to try something else + raise ValueError(f"Could not create effect upgrade for line {repr(line)}: " + "Out of order") + + armor_class = diff_armor_class.ref.value + attack_amount = diff_attack["amount"].value + + class_name = armor_lookup_dict[armor_class] + + patch_target_ref = f"{ability_ref}.Batch.{class_name}.ChangeAmount" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{class_name}AttackWrapper" + wrapper_ref = f"{tech_name}.{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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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(tech_group, tech_name)) + + # Nyan patch + nyan_patch_name = f"Change{class_name}Attack" + nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(tech_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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + attack_amount, + "engine.util.attribute.AttributeAmount", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + tech_group.add_raw_api_object(wrapper_raw_api_object) + tech_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_effect_subprocessor.py b/openage/convert/processor/conversion/aoc/upgrade_effect_subprocessor.py index f17b8c26dd..49a9473f10 100644 --- a/openage/convert/processor/conversion/aoc/upgrade_effect_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/upgrade_effect_subprocessor.py @@ -1,30 +1,11 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-statements,too-many-branches -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Upgrades effects and resistances for the Apply*Effect and Resistance abilities. """ -from __future__ import annotations -import typing - - -from .....nyan.nyan_structs import MemberOperator -from ....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ....value_object.read.value_members import NoDiffMember, \ - LeftMissingMember, RightMissingMember - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObject - from openage.convert.entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup +from .upgrade_effect.attack import get_attack_effects +from .upgrade_resistance.attack import get_attack_resistances class AoCUpgradeEffectSubprocessor: @@ -32,552 +13,5 @@ class AoCUpgradeEffectSubprocessor: Creates raw API objects for attack/resistance upgrades in AoC. """ - @staticmethod - def get_attack_effects( - tech_group: GenieTechEffectBundleGroup, - line: GenieGameEntityGroup, - diff: ConverterObject, - ability_ref: str - ) -> list[ForwardRef]: - """ - Upgrades effects that are used for attacking (unit command: 7) - - :param tech_group: Tech that gets the patch. - :type tech_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str - :returns: The forward references for the effects. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = tech_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - tech_name = tech_lookup_dict[tech_id][0] - - diff_attacks = diff["attacks"].value - for diff_attack in diff_attacks.values(): - if isinstance(diff_attack, NoDiffMember): - continue - - if isinstance(diff_attack, LeftMissingMember): - # Create a new attack effect, then patch it in - attack = diff_attack.ref - - armor_class = attack["type_id"].value - attack_amount = attack["amount"].value - - if armor_class == -1: - continue - - class_name = armor_lookup_dict[armor_class] - - # FlatAttributeChangeDecrease - effect_parent = "engine.effect.discrete.flat_attribute_change.FlatAttributeChange" - attack_parent = "engine.effect.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" - - patch_target_ref = f"{ability_ref}.Batch" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Add{class_name}AttackEffectWrapper" - wrapper_ref = f"{tech_name}.{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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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(tech_group, tech_name)) - - # Nyan patch - nyan_patch_name = f"Add{class_name}AttackEffect" - nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(tech_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) - - # New attack effect - # ============================================================================ - attack_ref = f"{nyan_patch_ref}.{class_name}" - attack_raw_api_object = RawAPIObject(attack_ref, - class_name, - dataset.nyan_api_objects) - attack_raw_api_object.add_raw_parent(attack_parent) - attack_location = ForwardRef(tech_group, nyan_patch_ref) - attack_raw_api_object.set_location(attack_location) - - # Type - type_ref = f"util.attribute_change_type.types.{class_name}" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - attack_raw_api_object.add_raw_member("type", - change_type, - effect_parent) - - # Min value (optional) - min_value = dataset.pregen_nyan_objects[("effect.discrete.flat_attribute_change." - "min_damage.AoE2MinChangeAmount")].get_nyan_object() - attack_raw_api_object.add_raw_member("min_change_value", - min_value, - effect_parent) - - # Max value (optional; not added because there is none in AoE2) - - # Change value - # ================================================================================= - amount_name = f"{nyan_patch_ref}.{class_name}.ChangeAmount" - amount_raw_api_object = RawAPIObject( - amount_name, "ChangeAmount", dataset.nyan_api_objects) - amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") - amount_location = ForwardRef(line, attack_ref) - amount_raw_api_object.set_location(amount_location) - - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object( - ) - amount_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeAmount") - amount_raw_api_object.add_raw_member("amount", - attack_amount, - "engine.util.attribute.AttributeAmount") - - line.add_raw_api_object(amount_raw_api_object) - # ================================================================================= - amount_forward_ref = ForwardRef(line, amount_name) - attack_raw_api_object.add_raw_member("change_value", - amount_forward_ref, - effect_parent) - - # Ignore protection - attack_raw_api_object.add_raw_member("ignore_protection", - [], - effect_parent) - - # Effect is added to the line, so it can be referenced by other upgrades - line.add_raw_api_object(attack_raw_api_object) - # ============================================================================ - attack_forward_ref = ForwardRef(line, attack_ref) - nyan_patch_raw_api_object.add_raw_patch_member("effects", - [attack_forward_ref], - "engine.util.effect_batch.EffectBatch", - MemberOperator.ADD) - - patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - tech_group.add_raw_api_object(wrapper_raw_api_object) - tech_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - elif isinstance(diff_attack, RightMissingMember): - # Patch the effect out of the ability - attack = diff_attack.ref - - armor_class = attack["type_id"].value - class_name = armor_lookup_dict[armor_class] - - patch_target_ref = f"{ability_ref}.Batch" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Remove{class_name}AttackEffectWrapper" - wrapper_ref = f"{tech_name}.{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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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(tech_group, tech_name)) - - # Nyan patch - nyan_patch_name = f"Remove{class_name}AttackEffect" - nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(tech_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) - - attack_ref = f"{ability_ref}.{class_name}" - attack_forward_ref = ForwardRef(line, attack_ref) - nyan_patch_raw_api_object.add_raw_patch_member("effects", - [attack_forward_ref], - "engine.util.effect_batch.EffectBatch", - MemberOperator.SUBTRACT) - - patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - tech_group.add_raw_api_object(wrapper_raw_api_object) - tech_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - else: - diff_armor_class = diff_attack["type_id"] - if not isinstance(diff_armor_class, NoDiffMember): - # If this happens then the attacks are out of order - # and we have to try something else - raise ValueError(f"Could not create effect upgrade for line {repr(line)}: " - "Out of order") - - armor_class = diff_armor_class.ref.value - attack_amount = diff_attack["amount"].value - - class_name = armor_lookup_dict[armor_class] - - patch_target_ref = f"{ability_ref}.Batch.{class_name}.ChangeAmount" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{class_name}AttackWrapper" - wrapper_ref = f"{tech_name}.{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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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(tech_group, tech_name)) - - # Nyan patch - nyan_patch_name = f"Change{class_name}Attack" - nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(tech_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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - attack_amount, - "engine.util.attribute.AttributeAmount", - MemberOperator.ADD) - - patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - tech_group.add_raw_api_object(wrapper_raw_api_object) - tech_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - return patches - - @staticmethod - def get_attack_resistances( - tech_group: GenieTechEffectBundleGroup, - line: GenieGameEntityGroup, - diff: ConverterObject, - ability_ref: str - ) -> list[ForwardRef]: - """ - Upgrades resistances that are used for attacking (unit command: 7) - - :param tech_group: Tech that gets the patch. - :type tech_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :param ability_ref: Reference of the ability raw API object the effects are added to. - :type ability_ref: str - :returns: The forward references for the resistances. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = tech_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - tech_name = tech_lookup_dict[tech_id][0] - - diff_armors = diff["armors"].value - for diff_armor in diff_armors.values(): - if isinstance(diff_armor, NoDiffMember): - continue - - if isinstance(diff_armor, LeftMissingMember): - # Create a new attack resistance, then patch it in - armor = diff_armor.ref - - armor_class = armor["type_id"].value - armor_amount = armor["amount"].value - - if armor_class == -1: - continue - - class_name = armor_lookup_dict[armor_class] - - # FlatAttributeChangeDecrease - resistance_parent = "engine.resistance.discrete.flat_attribute_change.FlatAttributeChange" - armor_parent = "engine.resistance.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" - - patch_target_ref = f"{ability_ref}" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Add{class_name}AttackResistanceWrapper" - wrapper_ref = f"{tech_name}.{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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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(tech_group, tech_name)) - - # Nyan patch - nyan_patch_name = f"Add{class_name}AttackResistance" - nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(tech_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) - - # New attack effect - # ============================================================================ - attack_ref = f"{nyan_patch_ref}.{class_name}" - attack_raw_api_object = RawAPIObject(attack_ref, - class_name, - dataset.nyan_api_objects) - attack_raw_api_object.add_raw_parent(armor_parent) - attack_location = ForwardRef(tech_group, nyan_patch_ref) - attack_raw_api_object.set_location(attack_location) - - # Type - type_ref = f"util.attribute_change_type.types.{class_name}" - change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() - attack_raw_api_object.add_raw_member("type", - change_type, - resistance_parent) - - # Block value - # ================================================================================= - amount_name = f"{nyan_patch_ref}.{class_name}.BlockAmount" - amount_raw_api_object = RawAPIObject( - amount_name, "BlockAmount", dataset.nyan_api_objects) - amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") - amount_location = ForwardRef(line, attack_ref) - amount_raw_api_object.set_location(amount_location) - - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object( - ) - amount_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeAmount") - amount_raw_api_object.add_raw_member("amount", - armor_amount, - "engine.util.attribute.AttributeAmount") - - line.add_raw_api_object(amount_raw_api_object) - # ================================================================================= - amount_forward_ref = ForwardRef(line, amount_name) - attack_raw_api_object.add_raw_member("block_value", - amount_forward_ref, - resistance_parent) - - # Resistance is added to the line, so it can be referenced by other upgrades - line.add_raw_api_object(attack_raw_api_object) - # ============================================================================ - attack_forward_ref = ForwardRef(line, attack_ref) - nyan_patch_raw_api_object.add_raw_patch_member("resistances", - [attack_forward_ref], - "engine.ability.type.Resistance", - MemberOperator.ADD) - - patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - tech_group.add_raw_api_object(wrapper_raw_api_object) - tech_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - elif isinstance(diff_armor, RightMissingMember): - # Patch the resistance out of the ability - armor = diff_armor.ref - - armor_class = armor["type_id"].value - class_name = armor_lookup_dict[armor_class] - - patch_target_ref = f"{ability_ref}" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Remove{class_name}AttackResistanceWrapper" - wrapper_ref = f"{tech_name}.{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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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(tech_group, tech_name)) - - # Nyan patch - nyan_patch_name = f"Remove{class_name}AttackResistance" - nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(tech_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) - - attack_ref = f"{ability_ref}.{class_name}" - attack_forward_ref = ForwardRef(line, attack_ref) - nyan_patch_raw_api_object.add_raw_patch_member("resistances", - [attack_forward_ref], - "engine.ability.type.Resistance", - MemberOperator.SUBTRACT) - - patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - tech_group.add_raw_api_object(wrapper_raw_api_object) - tech_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - else: - diff_armor_class = diff_armor["type_id"] - if not isinstance(diff_armor_class, NoDiffMember): - # If this happens then the armors are out of order - # and we have to try something else - raise ValueError(f"Could not create effect upgrade for line {repr(line)}: " - "Out of order") - - armor_class = diff_armor_class.ref.value - armor_amount = diff_armor["amount"].value - - class_name = armor_lookup_dict[armor_class] - - patch_target_ref = f"{ability_ref}.{class_name}.BlockAmount" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{class_name}ResistanceWrapper" - wrapper_ref = f"{tech_name}.{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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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(tech_group, tech_name)) - - # Nyan patch - nyan_patch_name = f"Change{class_name}Resistance" - nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(tech_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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - armor_amount, - "engine.util.attribute.AttributeAmount", - MemberOperator.ADD) - - patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - tech_group.add_raw_api_object(wrapper_raw_api_object) - tech_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - return patches + get_attack_effects = staticmethod(get_attack_effects) + get_attack_resistances = staticmethod(get_attack_resistances) diff --git a/openage/convert/processor/conversion/aoc/upgrade_resistance/CMakeLists.txt b/openage/convert/processor/conversion/aoc/upgrade_resistance/CMakeLists.txt new file mode 100644 index 0000000000..40b138d46a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resistance/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + attack.py +) diff --git a/openage/convert/processor/conversion/aoc/upgrade_resistance/__init__.py b/openage/convert/processor/conversion/aoc/upgrade_resistance/__init__.py new file mode 100644 index 0000000000..34b370f96c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resistance/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create nyan patches for upgrading resistances of abilities of AoC entities. +""" diff --git a/openage/convert/processor/conversion/aoc/upgrade_resistance/attack.py b/openage/convert/processor/conversion/aoc/upgrade_resistance/attack.py new file mode 100644 index 0000000000..c234738384 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resistance/attack.py @@ -0,0 +1,288 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Upgrades attack resistances of AoC entities. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember, LeftMissingMember, \ + RightMissingMember + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject + from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + +RESISTANCE_PARENT = "engine.resistance.discrete.flat_attribute_change.FlatAttributeChange" +ARMOR_PARENT = "engine.resistance.discrete.flat_attribute_change.type.FlatAttributeChangeDecrease" + + +def get_attack_resistances( + tech_group: GenieTechEffectBundleGroup, + line: GenieGameEntityGroup, + diff: ConverterObject, + ability_ref: str +) -> list[ForwardRef]: + """ + Upgrades resistances that are used for attacking (unit command: 7) + + :param tech_group: Tech that gets the patch. + :type tech_group: ...dataformat.converter_object.ConverterObjectGroup + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param diff: A diff between two ConvertObject instances. + :type diff: ...dataformat.converter_object.ConverterObject + :param ability_ref: Reference of the ability raw API object the effects are added to. + :type ability_ref: str + :returns: The forward references for the resistances. + :rtype: list + """ + head_unit_id = line.get_head_unit_id() + tech_id = tech_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + tech_name = tech_lookup_dict[tech_id][0] + + diff_armors = diff["armors"].value + for diff_armor in diff_armors.values(): + if isinstance(diff_armor, NoDiffMember): + continue + + if isinstance(diff_armor, LeftMissingMember): + # Create a new attack resistance, then patch it in + armor = diff_armor.ref + + armor_class = armor["type_id"].value + armor_amount = armor["amount"].value + + if armor_class == -1: + continue + + class_name = armor_lookup_dict[armor_class] + + # FlatAttributeChangeDecrease + patch_target_ref = f"{ability_ref}" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Add{class_name}AttackResistanceWrapper" + wrapper_ref = f"{tech_name}.{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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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(tech_group, tech_name)) + + # Nyan patch + nyan_patch_name = f"Add{class_name}AttackResistance" + nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(tech_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) + + # New attack effect + # ============================================================================ + attack_ref = f"{nyan_patch_ref}.{class_name}" + attack_raw_api_object = RawAPIObject(attack_ref, + class_name, + dataset.nyan_api_objects) + attack_raw_api_object.add_raw_parent(ARMOR_PARENT) + attack_location = ForwardRef(tech_group, nyan_patch_ref) + attack_raw_api_object.set_location(attack_location) + + # Type + type_ref = f"util.attribute_change_type.types.{class_name}" + change_type = dataset.pregen_nyan_objects[type_ref].get_nyan_object() + attack_raw_api_object.add_raw_member("type", + change_type, + RESISTANCE_PARENT) + + # Block value + # ================================================================================= + amount_name = f"{nyan_patch_ref}.{class_name}.BlockAmount" + amount_raw_api_object = RawAPIObject( + amount_name, "BlockAmount", dataset.nyan_api_objects) + amount_raw_api_object.add_raw_parent("engine.util.attribute.AttributeAmount") + amount_location = ForwardRef(line, attack_ref) + amount_raw_api_object.set_location(amount_location) + + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object( + ) + amount_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeAmount") + amount_raw_api_object.add_raw_member("amount", + armor_amount, + "engine.util.attribute.AttributeAmount") + + line.add_raw_api_object(amount_raw_api_object) + # ================================================================================= + amount_forward_ref = ForwardRef(line, amount_name) + attack_raw_api_object.add_raw_member("block_value", + amount_forward_ref, + RESISTANCE_PARENT) + + # Resistance is added to the line, so it can be referenced by other upgrades + line.add_raw_api_object(attack_raw_api_object) + # ============================================================================ + attack_forward_ref = ForwardRef(line, attack_ref) + nyan_patch_raw_api_object.add_raw_patch_member("resistances", + [attack_forward_ref], + "engine.ability.type.Resistance", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + tech_group.add_raw_api_object(wrapper_raw_api_object) + tech_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + elif isinstance(diff_armor, RightMissingMember): + # Patch the resistance out of the ability + armor = diff_armor.ref + + armor_class = armor["type_id"].value + class_name = armor_lookup_dict[armor_class] + + patch_target_ref = f"{ability_ref}" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Remove{class_name}AttackResistanceWrapper" + wrapper_ref = f"{tech_name}.{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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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(tech_group, tech_name)) + + # Nyan patch + nyan_patch_name = f"Remove{class_name}AttackResistance" + nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(tech_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) + + attack_ref = f"{ability_ref}.{class_name}" + attack_forward_ref = ForwardRef(line, attack_ref) + nyan_patch_raw_api_object.add_raw_patch_member("resistances", + [attack_forward_ref], + "engine.ability.type.Resistance", + MemberOperator.SUBTRACT) + + patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + tech_group.add_raw_api_object(wrapper_raw_api_object) + tech_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + else: + diff_armor_class = diff_armor["type_id"] + if not isinstance(diff_armor_class, NoDiffMember): + # If this happens then the armors are out of order + # and we have to try something else + raise ValueError(f"Could not create effect upgrade for line {repr(line)}: " + "Out of order") + + armor_class = diff_armor_class.ref.value + armor_amount = diff_armor["amount"].value + + class_name = armor_lookup_dict[armor_class] + + patch_target_ref = f"{ability_ref}.{class_name}.BlockAmount" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{class_name}ResistanceWrapper" + wrapper_ref = f"{tech_name}.{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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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(tech_group, tech_name)) + + # Nyan patch + nyan_patch_name = f"Change{class_name}Resistance" + nyan_patch_ref = f"{tech_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(tech_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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + armor_amount, + "engine.util.attribute.AttributeAmount", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(tech_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + tech_group.add_raw_api_object(wrapper_raw_api_object) + tech_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(tech_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + return patches From 125b0add7d37db542c5f0813684f6cc2144beab9 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 8 Jun 2025 23:20:47 +0200 Subject: [PATCH 122/163] convert: Refactor AoCUpgradeAttributeSubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../aoc/upgrade_attribute/CMakeLists.txt | 42 + .../aoc/upgrade_attribute/__init__.py | 5 + .../aoc/upgrade_attribute/accuracy.py | 104 + .../conversion/aoc/upgrade_attribute/armor.py | 122 + .../aoc/upgrade_attribute/attack.py | 132 + .../upgrade_attribute/attack_warning_sound.py | 37 + .../aoc/upgrade_attribute/ballistics.py | 175 + .../aoc/upgrade_attribute/blast_radius.py | 37 + .../aoc/upgrade_attribute/carry_capacity.py | 37 + .../aoc/upgrade_attribute/cost_food.py | 117 + .../aoc/upgrade_attribute/cost_gold.py | 117 + .../aoc/upgrade_attribute/cost_stone.py | 117 + .../aoc/upgrade_attribute/cost_wood.py | 117 + .../aoc/upgrade_attribute/creation_time.py | 104 + .../upgrade_attribute/garrison_capacity.py | 108 + .../aoc/upgrade_attribute/garrison_heal.py | 37 + .../aoc/upgrade_attribute/gold_counter.py | 37 + .../aoc/upgrade_attribute/graphics_angle.py | 37 + .../aoc/upgrade_attribute/health.py | 109 + .../aoc/upgrade_attribute/ignore_armor.py | 37 + .../aoc/upgrade_attribute/imperial_tech_id.py | 37 + .../aoc/upgrade_attribute/kidnap_storage.py | 37 + .../aoc/upgrade_attribute/line_of_sight.py | 104 + .../aoc/upgrade_attribute/max_projectiles.py | 104 + .../aoc/upgrade_attribute/max_range.py | 123 + .../aoc/upgrade_attribute/min_projectiles.py | 104 + .../aoc/upgrade_attribute/min_range.py | 117 + .../aoc/upgrade_attribute/move_speed.py | 104 + .../aoc/upgrade_attribute/projectile_unit.py | 37 + .../aoc/upgrade_attribute/reload_time.py | 120 + .../aoc/upgrade_attribute/resource_cost.py | 134 + .../upgrade_attribute/resource_storage_1.py | 123 + .../aoc/upgrade_attribute/rotation_speed.py | 37 + .../aoc/upgrade_attribute/search_radius.py | 112 + .../aoc/upgrade_attribute/standing_wonders.py | 37 + .../aoc/upgrade_attribute/tc_available.py | 37 + .../aoc/upgrade_attribute/terrain_defense.py | 37 + .../aoc/upgrade_attribute/train_button.py | 37 + .../upgrade_attribute/tribute_inefficiency.py | 37 + .../aoc/upgrade_attribute/unit_size.py | 60 + .../aoc/upgrade_attribute/work_rate.py | 37 + .../aoc/upgrade_attribute_subprocessor.py | 2838 +---------------- 43 files changed, 3283 insertions(+), 2759 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/accuracy.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/armor.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/attack.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/attack_warning_sound.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/ballistics.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/blast_radius.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/carry_capacity.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/cost_food.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/cost_gold.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/cost_stone.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/cost_wood.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/creation_time.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/garrison_capacity.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/garrison_heal.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/gold_counter.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/graphics_angle.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/health.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/ignore_armor.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/imperial_tech_id.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/kidnap_storage.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/line_of_sight.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/max_projectiles.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/max_range.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/min_projectiles.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/min_range.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/move_speed.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/projectile_unit.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/reload_time.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/resource_cost.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/resource_storage_1.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/rotation_speed.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/search_radius.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/standing_wonders.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/tc_available.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/terrain_defense.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/train_button.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/tribute_inefficiency.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/unit_size.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_attribute/work_rate.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index 6eb0daa02c..de12475174 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -30,5 +30,6 @@ add_subdirectory(pregen) add_subdirectory(resistance) add_subdirectory(tech) add_subdirectory(upgrade_ability) +add_subdirectory(upgrade_attribute) add_subdirectory(upgrade_effect) add_subdirectory(upgrade_resistance) diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/CMakeLists.txt b/openage/convert/processor/conversion/aoc/upgrade_attribute/CMakeLists.txt new file mode 100644 index 0000000000..8bc0bb46b2 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/CMakeLists.txt @@ -0,0 +1,42 @@ +add_py_modules( + __init__.py + accuracy.py + armor.py + attack.py + attack_warning_sound.py + ballistics.py + blast_radius.py + carry_capacity.py + cost_food.py + cost_gold.py + cost_stone.py + cost_wood.py + creation_time.py + garrison_capacity.py + garrison_heal.py + gold_counter.py + graphics_angle.py + health.py + ignore_armor.py + imperial_tech_id.py + kidnap_storage.py + line_of_sight.py + max_projectiles.py + max_range.py + min_projectiles.py + min_range.py + move_speed.py + projectile_unit.py + reload_time.py + resource_cost.py + resource_storage_1.py + rotation_speed.py + search_radius.py + standing_wonders.py + tc_available.py + terrain_defense.py + train_button.py + tribute_inefficiency.py + unit_size.py + work_rate.py +) diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/__init__.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/__init__.py new file mode 100644 index 0000000000..dfeb650ab6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create upgrade patches for attributes in AoC. +""" diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/accuracy.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/accuracy.py new file mode 100644 index 0000000000..88c6ffb672 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/accuracy.py @@ -0,0 +1,104 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for accuracy in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def accuracy_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the accuracy modify effect (ID: 11). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = f"{game_entity_name}.ShootProjectile.Projectile0.Projectile.Accuracy" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}AccuracyWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}Accuracy" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("accuracy", + value, + "engine.util.accuracy.Accuracy", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/armor.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/armor.py new file mode 100644 index 0000000000..59f92ed908 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/armor.py @@ -0,0 +1,122 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for armor classes in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def armor_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the armor modify effect (ID: 8). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + if value > 0: + armor_class = int(value) >> 8 + armor_amount = int(value) & 0x0F + + else: + # Sign is for armor amount + value *= -1 + armor_class = int(value) >> 8 + armor_amount = int(value) & 0x0F + armor_amount *= -1 + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + class_name = armor_lookup_dict[armor_class] + + if line.has_armor(armor_class): + patch_target_ref = f"{game_entity_name}.Resistance.{class_name}.BlockAmount" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + else: + # TODO: Create new attack resistance + return patches + + # Wrapper + wrapper_name = f"Change{game_entity_name}{class_name}ResistanceWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}{class_name}Resistance" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + armor_amount, + "engine.util.attribute.AttributeAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/attack.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/attack.py new file mode 100644 index 0000000000..982de758b0 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/attack.py @@ -0,0 +1,132 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for attack damage in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def attack_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the attack modify effect (ID: 9). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + attack_amount = int(value) & 0x0F + armor_class = int(value) >> 8 + + if armor_class == -1: + return patches + + if not line.has_armor(armor_class): + # TODO: Happens sometimes in AoE1; what do we do? + return patches + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + class_name = armor_lookup_dict[armor_class] + + if line.is_projectile_shooter(): + primary_projectile_id = line.get_head_unit( + )["projectile_id0"].value + if primary_projectile_id == -1: + # Upgrade is skipped if the primary projectile is not defined + return patches + + patch_target_ref = (f"{game_entity_name}.ShootProjectile.Projectile0." + f"Attack.Batch.{class_name}.ChangeAmount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + else: + patch_target_ref = f"{game_entity_name}.Attack.Batch.{class_name}.ChangeAmount" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + if not line.has_attack(armor_class): + # TODO: Create new attack effect + return patches + + # Wrapper + wrapper_name = f"Change{game_entity_name}{class_name}AttackWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}{class_name}Attack" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + attack_amount, + "engine.util.attribute.AttributeAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/attack_warning_sound.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/attack_warning_sound.py new file mode 100644 index 0000000000..1359c09939 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/attack_warning_sound.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the attack warning sound in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def attack_warning_sound_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the attack warning sound modify effect (ID: 26). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/ballistics.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/ballistics.py new file mode 100644 index 0000000000..cfc0f7b759 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/ballistics.py @@ -0,0 +1,175 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for ballistics tech in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def ballistics_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: int, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the ballistics modify effect (ID: 19). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit = line.get_head_unit() + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + target_mode = None + if value == 0: + target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.CurrentPosition"] + + elif value == 1: + target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.ExpectedPosition"] + + elif value == 2: + # No Ballistics, only for Arambai + target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.ExpectedPosition"] + + elif value == 3: + # Ballistics, only for Arambai + target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.ExpectedPosition"] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + projectile_id0 = head_unit["projectile_id0"].value + projectile_id1 = head_unit["projectile_id1"].value + + if projectile_id0 > -1: + patch_target_ref = f"{game_entity_name}.ShootProjectile.Projectile0.Projectile" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}Projectile0TargetModeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}Projectile0TargetMode" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("target_mode", + target_mode, + "engine.ability.type.Projectile", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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 projectile_id1 > -1: + patch_target_ref = f"{game_entity_name}.ShootProjectile.Projectile1.Projectile" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}Projectile1TargetModeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}Projectile1TargetMode" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("target_mode", + target_mode, + "engine.ability.type.Projectile", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/blast_radius.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/blast_radius.py new file mode 100644 index 0000000000..d121e12da6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/blast_radius.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the blast radius in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def blast_radius_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the blast radius modify effect (ID: 22). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/carry_capacity.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/carry_capacity.py new file mode 100644 index 0000000000..8a0c88d854 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/carry_capacity.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the carry capacity in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def carry_capacity_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the carry capacity modify effect (ID: 14). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_food.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_food.py new file mode 100644 index 0000000000..d2992f33d5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_food.py @@ -0,0 +1,117 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for food cost amounts in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def cost_food_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the food cost modify effect (ID: 103). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit = line.get_head_unit() + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + # Check if the unit line actually costs food + for resource_amount in head_unit["resource_cost"].value: + resource_id = resource_amount["type_id"].value + + if resource_id == 0: + break + + else: + # Skip patch generation if no food cost was found + return patches + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}Cost.FoodAmount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}FoodCostWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}FoodCost" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_gold.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_gold.py new file mode 100644 index 0000000000..a9aeefb69a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_gold.py @@ -0,0 +1,117 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for gold cost amounts in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def cost_gold_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the gold cost modify effect (ID: 105). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit = line.get_head_unit() + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + # Check if the unit line actually costs gold + for resource_amount in head_unit["resource_cost"].value: + resource_id = resource_amount["type_id"].value + + if resource_id == 3: + break + + else: + # Skip patch generation if no gold cost was found + return patches + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}Cost.GoldAmount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}GoldCostWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}GoldCost" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_stone.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_stone.py new file mode 100644 index 0000000000..a82d56f173 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_stone.py @@ -0,0 +1,117 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for stone cost amounts in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def cost_stone_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the stone cost modify effect (ID: 106). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit = line.get_head_unit() + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + # Check if the unit line actually costs stone + for resource_amount in head_unit["resource_cost"].value: + resource_id = resource_amount["type_id"].value + + if resource_id == 2: + break + + else: + # Skip patch generation if no stone cost was found + return patches + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}Cost.StoneAmount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}StoneCostWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}StoneCost" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_wood.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_wood.py new file mode 100644 index 0000000000..56e6283f05 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/cost_wood.py @@ -0,0 +1,117 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for wood cost amounts in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def cost_wood_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the wood cost modify effect (ID: 104). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit = line.get_head_unit() + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + # Check if the unit line actually costs wood + for resource_amount in head_unit["resource_cost"].value: + resource_id = resource_amount["type_id"].value + + if resource_id == 1: + break + + else: + # Skip patch generation if no wood cost was found + return patches + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}Cost.WoodAmount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}WoodCostWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}WoodCost" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/creation_time.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/creation_time.py new file mode 100644 index 0000000000..f09e5fc86b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/creation_time.py @@ -0,0 +1,104 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for creation times in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def creation_time_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the creation time modify effect (ID: 101). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = f"{game_entity_name}.CreatableGameEntity" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}CreationTimeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}CreationTime" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("creation_time", + value, + "engine.util.create.CreatableGameEntity", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/garrison_capacity.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/garrison_capacity.py new file mode 100644 index 0000000000..c53fd274cc --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/garrison_capacity.py @@ -0,0 +1,108 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for garrison capacity in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def garrison_capacity_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the garrison capacity modify effect (ID: 2). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if not line.is_garrison(): + # TODO: Patch ability in + return patches + + patch_target_ref = f"{game_entity_name}.Storage.{game_entity_name}Container" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}CreationTimeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}CreationTime" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("slots", + value, + "engine.util.storage.EntityContainer", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/garrison_heal.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/garrison_heal.py new file mode 100644 index 0000000000..2e8b957bff --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/garrison_heal.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for garrison heal rates in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def garrison_heal_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the garrison heal rate modify effect (ID: 108). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/gold_counter.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/gold_counter.py new file mode 100644 index 0000000000..1119e20a2f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/gold_counter.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for gold counter in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def gold_counter_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the gold counter effect (ID: 49). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/graphics_angle.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/graphics_angle.py new file mode 100644 index 0000000000..ec98c57332 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/graphics_angle.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for graphics angles in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def graphics_angle_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: int, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the graphics angle modify effect (ID: 17). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/health.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/health.py new file mode 100644 index 0000000000..24ceb3eed6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/health.py @@ -0,0 +1,109 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for max health attribute changes in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def health_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the HP modify effect (ID: 0). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = f"{game_entity_name}.Live.Health" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}MaxHealthWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}MaxHealth" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("max_value", + value, + "engine.util.attribute.AttributeSetting", + operator) + + nyan_patch_raw_api_object.add_raw_patch_member("starting_value", + value, + "engine.util.attribute.AttributeSetting", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/ignore_armor.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/ignore_armor.py new file mode 100644 index 0000000000..df8a3c70c2 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/ignore_armor.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for armor ignoration in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def ignore_armor_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the ignore armor effect (ID: 63). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/imperial_tech_id.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/imperial_tech_id.py new file mode 100644 index 0000000000..ab642f0f5d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/imperial_tech_id.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the imperial tech ID in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def imperial_tech_id_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the imperial tech ID effect (ID: 24). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/kidnap_storage.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/kidnap_storage.py new file mode 100644 index 0000000000..0cb1c88361 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/kidnap_storage.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for kidnap storage in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def kidnap_storage_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the kidnap storage effect (ID: 57). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/line_of_sight.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/line_of_sight.py new file mode 100644 index 0000000000..735287b71a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/line_of_sight.py @@ -0,0 +1,104 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for line of sight in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def line_of_sight_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the line of sight modify effect (ID: 1). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = f"{game_entity_name}.LineOfSight" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}LineOfSightWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}LineOfSight" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("range", + value, + "engine.ability.type.LineOfSight", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/max_projectiles.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/max_projectiles.py new file mode 100644 index 0000000000..a92b1eb8d6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/max_projectiles.py @@ -0,0 +1,104 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the maximum number of projectiles in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def max_projectiles_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the max projectiles modify effect (ID: 107). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = f"{game_entity_name}.Attack" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}MaxProjectilesWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}MaxProjectiles" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("max_projectiles", + value, + "engine.ability.type.ShootProjectile", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/max_range.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/max_range.py new file mode 100644 index 0000000000..6640ec2b79 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/max_range.py @@ -0,0 +1,123 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for max range in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def max_range_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the max range modify effect (ID: 12). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + 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.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + elif line.is_melee(): + if line.is_ranged(): + patch_target_ref = f"{game_entity_name}.Attack.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + else: + # excludes ram upgrades + return patches + + elif line.has_command(104): + patch_target_ref = f"{game_entity_name}.Convert.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + else: + # no matching ability + return patches + + # Wrapper + wrapper_name = f"Change{game_entity_name}MaxRangeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}MaxRange" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("max_range", + value, + patch_target_parent, + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/min_projectiles.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/min_projectiles.py new file mode 100644 index 0000000000..929d227e47 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/min_projectiles.py @@ -0,0 +1,104 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the minimum number of projectiles in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def min_projectiles_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the min projectiles modify effect (ID: 102). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = f"{game_entity_name}.Attack" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}MinProjectilesWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}MinProjectiles" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("min_projectiles", + value, + "engine.ability.type.ShootProjectile", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/min_range.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/min_range.py new file mode 100644 index 0000000000..d7d8d0f28f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/min_range.py @@ -0,0 +1,117 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for min range in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def min_range_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the min range modify effect (ID: 20). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + 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.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + elif line.is_melee(): + patch_target_ref = f"{game_entity_name}.Attack.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + elif line.has_command(104): + patch_target_ref = f"{game_entity_name}.Convert.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + else: + return [] + + # Wrapper + wrapper_name = f"Change{game_entity_name}MinRangeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}MinRange" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("min_range", + value, + patch_target_parent, + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/move_speed.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/move_speed.py new file mode 100644 index 0000000000..4172f7d7c9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/move_speed.py @@ -0,0 +1,104 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for move speed in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def move_speed_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the move speed modify effect (ID: 5). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = f"{game_entity_name}.Move" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}MoveSpeedWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}MoveSpeed" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("speed", + value, + "engine.ability.type.Move", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/projectile_unit.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/projectile_unit.py new file mode 100644 index 0000000000..2696136d84 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/projectile_unit.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for projectile units in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def projectile_unit_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the projectile modify effect (ID: 16). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/reload_time.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/reload_time.py new file mode 100644 index 0000000000..33fc443414 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/reload_time.py @@ -0,0 +1,120 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for reload time in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def reload_time_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the reload time modify effect (ID: 10). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if line.is_projectile_shooter(): + patch_target_ref = f"{game_entity_name}.Attack" + 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_forward_ref = ForwardRef(line, patch_target_ref) + patch_target_parent = "engine.ability.type.ApplyDiscreteEffect" + + elif line.has_command(104): + patch_target_ref = f"{game_entity_name}.Convert" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + patch_target_parent = "engine.ability.type.ApplyDiscreteEffect" + + else: + # No matching ability + return patches + + # Wrapper + wrapper_name = f"Change{game_entity_name}ReloadTimeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}ReloadTime" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("reload_time", + value, + patch_target_parent, + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/resource_cost.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/resource_cost.py new file mode 100644 index 0000000000..dbd04ad9c7 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/resource_cost.py @@ -0,0 +1,134 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for resource cost in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def resource_cost_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the resource modify effect (ID: 100). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit = line.get_head_unit() + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + for resource_amount in head_unit["resource_cost"].value: + resource_id = resource_amount["type_id"].value + + resource_name = "" + if resource_id == -1: + # Not a valid resource + continue + + if resource_id == 0: + resource_name = "Food" + + elif resource_id == 1: + resource_name = "Wood" + + elif resource_id == 2: + resource_name = "Stone" + + elif resource_id == 3: + resource_name = "Gold" + + else: + # Other resource ids are handled differently + continue + + # Skip resources that are only expected to be there + if not resource_amount["enabled"].value: + continue + + patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}Cost.{resource_name}Amount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{resource_name}CostWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}{resource_name}Cost" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/resource_storage_1.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/resource_storage_1.py new file mode 100644 index 0000000000..a001d4abc0 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/resource_storage_1.py @@ -0,0 +1,123 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for resource storage 1 in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def resource_storage_1_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the resource storage 1 modify effect (ID: 21). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param line: Unit/Building line that has the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: int, float + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + if line.is_harvestable(): + patch_target_ref = f"{game_entity_name}.Harvestable.{game_entity_name}ResourceSpot" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + wrapper_name = f"Change{game_entity_name}HarvestableAmountWrapper" + nyan_patch_name = f"Change{game_entity_name}HarvestableAmount" + + else: + patch_target_ref = f"{game_entity_name}.ProvideContingent.PopSpace" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + wrapper_name = f"Change{game_entity_name}PopSpaceWrapper" + nyan_patch_name = f"Change{game_entity_name}PopSpace" + + # Wrapper + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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 line.is_harvestable(): + nyan_patch_raw_api_object.add_raw_patch_member("max_amount", + value, + "engine.util.resource_spot.ResourceSpot", + operator) + + else: + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/rotation_speed.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/rotation_speed.py new file mode 100644 index 0000000000..52623db72e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/rotation_speed.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for rotation speed in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def rotation_speed_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the move speed modify effect (ID: 6). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/search_radius.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/search_radius.py new file mode 100644 index 0000000000..81a7bf25fe --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/search_radius.py @@ -0,0 +1,112 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the unit search radius in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def search_radius_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the search radius modify effect (ID: 23). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + if isinstance(line, GenieBuildingLineGroup) and not line.is_projectile_shooter(): + # Does not have the ability + return patches + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + stance_names = ["Aggressive", "Defensive", "StandGround", "Passive"] + + for stance_name in stance_names: + patch_target_ref = f"{game_entity_name}.GameEntityStance.{stance_name}" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{stance_name}SearchRangeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}{stance_name}SearchRange" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("search_range", + value, + "engine.util.game_entity_stance.GameEntityStance", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/standing_wonders.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/standing_wonders.py new file mode 100644 index 0000000000..2435aa8135 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/standing_wonders.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for standing wonders effects in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def standing_wonders_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the standing wonders effect (ID: 42). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/tc_available.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/tc_available.py new file mode 100644 index 0000000000..5af0f84a24 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/tc_available.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for TC available effects in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def tc_available_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the TC available effect (ID: 48). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/terrain_defense.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/terrain_defense.py new file mode 100644 index 0000000000..aaefa5ec2e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/terrain_defense.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for terrain defense in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def terrain_defense_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the terrain defense modify effect (ID: 18). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/train_button.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/train_button.py new file mode 100644 index 0000000000..d60e1a4ba9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/train_button.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for train buttons in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def train_button_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the train button modify effect (ID: 43). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/tribute_inefficiency.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/tribute_inefficiency.py new file mode 100644 index 0000000000..b94497c58b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/tribute_inefficiency.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for tribute inefficiency in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def tribute_inefficiency_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the tribute inefficiency effect (ID: 46). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/unit_size.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/unit_size.py new file mode 100644 index 0000000000..130b2e9043 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/unit_size.py @@ -0,0 +1,60 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for unit size in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def unit_size_x_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the unit size x modify effect (ID: 3). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches + + +def unit_size_y_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the unit size y modify effect (ID: 4). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute/work_rate.py b/openage/convert/processor/conversion/aoc/upgrade_attribute/work_rate.py new file mode 100644 index 0000000000..773ce00b67 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute/work_rate.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for work rates in AoC. +""" +from __future__ import annotations +import typing + +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def work_rate_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the work rate modify effect (ID: 13). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py b/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py index d50f33c8b8..8f07d2e46d 100644 --- a/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/upgrade_attribute_subprocessor.py @@ -1,27 +1,47 @@ # 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 -# -# TODO: Remove when all methods are implemented -# pylint: disable=unused-argument,line-too-long """ Creates upgrade patches for attribute modification effects in AoC. """ -from __future__ import annotations -import typing - - -from ....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup -from ....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup - from openage.nyan.nyan_structs import MemberOperator +from .upgrade_attribute.accuracy import accuracy_upgrade +from .upgrade_attribute.armor import armor_upgrade +from .upgrade_attribute.attack import attack_upgrade +from .upgrade_attribute.ballistics import ballistics_upgrade +from .upgrade_attribute.attack_warning_sound import attack_warning_sound_upgrade +from .upgrade_attribute.blast_radius import blast_radius_upgrade +from .upgrade_attribute.carry_capacity import carry_capacity_upgrade +from .upgrade_attribute.cost_food import cost_food_upgrade +from .upgrade_attribute.cost_wood import cost_wood_upgrade +from .upgrade_attribute.cost_gold import cost_gold_upgrade +from .upgrade_attribute.cost_stone import cost_stone_upgrade +from .upgrade_attribute.creation_time import creation_time_upgrade +from .upgrade_attribute.garrison_capacity import garrison_capacity_upgrade +from .upgrade_attribute.garrison_heal import garrison_heal_upgrade +from .upgrade_attribute.graphics_angle import graphics_angle_upgrade +from .upgrade_attribute.gold_counter import gold_counter_upgrade +from .upgrade_attribute.health import health_upgrade +from .upgrade_attribute.ignore_armor import ignore_armor_upgrade +from .upgrade_attribute.imperial_tech_id import imperial_tech_id_upgrade +from .upgrade_attribute.kidnap_storage import kidnap_storage_upgrade +from .upgrade_attribute.line_of_sight import line_of_sight_upgrade +from .upgrade_attribute.max_projectiles import max_projectiles_upgrade +from .upgrade_attribute.min_projectiles import min_projectiles_upgrade +from .upgrade_attribute.max_range import max_range_upgrade +from .upgrade_attribute.min_range import min_range_upgrade +from .upgrade_attribute.move_speed import move_speed_upgrade +from .upgrade_attribute.projectile_unit import projectile_unit_upgrade +from .upgrade_attribute.reload_time import reload_time_upgrade +from .upgrade_attribute.resource_cost import resource_cost_upgrade +from .upgrade_attribute.resource_storage_1 import resource_storage_1_upgrade +from .upgrade_attribute.rotation_speed import rotation_speed_upgrade +from .upgrade_attribute.search_radius import search_radius_upgrade +from .upgrade_attribute.standing_wonders import standing_wonders_upgrade +from .upgrade_attribute.tc_available import tc_available_upgrade +from .upgrade_attribute.terrain_defense import terrain_defense_upgrade +from .upgrade_attribute.train_button import train_button_upgrade +from .upgrade_attribute.tribute_inefficiency import tribute_inefficiency_upgrade +from .upgrade_attribute.unit_size import unit_size_x_upgrade, unit_size_y_upgrade +from .upgrade_attribute.work_rate import work_rate_upgrade class AoCUpgradeAttributeSubprocessor: @@ -29,2743 +49,43 @@ class AoCUpgradeAttributeSubprocessor: Creates raw API objects for attribute upgrade effects in AoC. """ - @staticmethod - def accuracy_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the accuracy modify effect (ID: 11). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = f"{game_entity_name}.ShootProjectile.Projectile0.Projectile.Accuracy" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}AccuracyWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}Accuracy" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("accuracy", - value, - "engine.util.accuracy.Accuracy", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def armor_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the armor modify effect (ID: 8). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - if value > 0: - armor_class = int(value) >> 8 - armor_amount = int(value) & 0x0F - - else: - # Sign is for armor amount - value *= -1 - armor_class = int(value) >> 8 - armor_amount = int(value) & 0x0F - armor_amount *= -1 - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - class_name = armor_lookup_dict[armor_class] - - if line.has_armor(armor_class): - patch_target_ref = f"{game_entity_name}.Resistance.{class_name}.BlockAmount" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - else: - # TODO: Create new attack resistance - return patches - - # Wrapper - wrapper_name = f"Change{game_entity_name}{class_name}ResistanceWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}{class_name}Resistance" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - armor_amount, - "engine.util.attribute.AttributeAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def attack_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the attack modify effect (ID: 9). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - attack_amount = int(value) & 0x0F - armor_class = int(value) >> 8 - - if armor_class == -1: - return patches - - if not line.has_armor(armor_class): - # TODO: Happens sometimes in AoE1; what do we do? - return patches - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - armor_lookup_dict = internal_name_lookups.get_armor_class_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - class_name = armor_lookup_dict[armor_class] - - if line.is_projectile_shooter(): - primary_projectile_id = line.get_head_unit( - )["projectile_id0"].value - if primary_projectile_id == -1: - # Upgrade is skipped if the primary projectile is not defined - return patches - - patch_target_ref = (f"{game_entity_name}.ShootProjectile.Projectile0." - f"Attack.Batch.{class_name}.ChangeAmount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - else: - patch_target_ref = f"{game_entity_name}.Attack.Batch.{class_name}.ChangeAmount" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - if not line.has_attack(armor_class): - # TODO: Create new attack effect - return patches - - # Wrapper - wrapper_name = f"Change{game_entity_name}{class_name}AttackWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}{class_name}Attack" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - attack_amount, - "engine.util.attribute.AttributeAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def ballistics_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: int, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the ballistics modify effect (ID: 19). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit = line.get_head_unit() - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - target_mode = None - if value == 0: - target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.CurrentPosition"] - - elif value == 1: - target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.ExpectedPosition"] - - elif value == 2: - # No Ballistics, only for Arambai - target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.ExpectedPosition"] - - elif value == 3: - # Ballistics, only for Arambai - target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.ExpectedPosition"] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - projectile_id0 = head_unit["projectile_id0"].value - projectile_id1 = head_unit["projectile_id1"].value - - if projectile_id0 > -1: - patch_target_ref = f"{game_entity_name}.ShootProjectile.Projectile0.Projectile" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}Projectile0TargetModeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}Projectile0TargetMode" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("target_mode", - target_mode, - "engine.ability.type.Projectile", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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 projectile_id1 > -1: - patch_target_ref = f"{game_entity_name}.ShootProjectile.Projectile1.Projectile" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}Projectile1TargetModeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}Projectile1TargetMode" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("target_mode", - target_mode, - "engine.ability.type.Projectile", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def attack_warning_sound_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the attack warning sound modify effect (ID: 26). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def blast_radius_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the blast radius modify effect (ID: 22). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def carry_capacity_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the carry capacity modify effect (ID: 14). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def cost_food_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the food cost modify effect (ID: 103). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit = line.get_head_unit() - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - # Check if the unit line actually costs food - for resource_amount in head_unit["resource_cost"].value: - resource_id = resource_amount["type_id"].value - - if resource_id == 0: - break - - else: - # Skip patch generation if no food cost was found - return patches - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}Cost.FoodAmount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}FoodCostWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}FoodCost" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def cost_wood_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the wood cost modify effect (ID: 104). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit = line.get_head_unit() - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - # Check if the unit line actually costs wood - for resource_amount in head_unit["resource_cost"].value: - resource_id = resource_amount["type_id"].value - - if resource_id == 1: - break - - else: - # Skip patch generation if no wood cost was found - return patches - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}Cost.WoodAmount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}WoodCostWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}WoodCost" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def cost_gold_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the gold cost modify effect (ID: 105). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit = line.get_head_unit() - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - # Check if the unit line actually costs gold - for resource_amount in head_unit["resource_cost"].value: - resource_id = resource_amount["type_id"].value - - if resource_id == 3: - break - - else: - # Skip patch generation if no gold cost was found - return patches - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}Cost.GoldAmount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}GoldCostWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}GoldCost" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def cost_stone_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the stone cost modify effect (ID: 106). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit = line.get_head_unit() - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - # Check if the unit line actually costs stone - for resource_amount in head_unit["resource_cost"].value: - resource_id = resource_amount["type_id"].value - - if resource_id == 2: - break - - else: - # Skip patch generation if no stone cost was found - return patches - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}Cost.StoneAmount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}StoneCostWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}StoneCost" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def creation_time_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the creation time modify effect (ID: 101). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = f"{game_entity_name}.CreatableGameEntity" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}CreationTimeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}CreationTime" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("creation_time", - value, - "engine.util.create.CreatableGameEntity", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def garrison_capacity_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the garrison capacity modify effect (ID: 2). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if not line.is_garrison(): - # TODO: Patch ability in - return patches - - patch_target_ref = f"{game_entity_name}.Storage.{game_entity_name}Container" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}CreationTimeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}CreationTime" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("slots", - value, - "engine.util.storage.EntityContainer", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def garrison_heal_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the garrison heal rate modify effect (ID: 108). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def graphics_angle_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: int, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the graphics angle modify effect (ID: 17). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def gold_counter_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the gold counter effect (ID: 49). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def hp_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the HP modify effect (ID: 0). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = f"{game_entity_name}.Live.Health" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}MaxHealthWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}MaxHealth" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("max_value", - value, - "engine.util.attribute.AttributeSetting", - operator) - - nyan_patch_raw_api_object.add_raw_patch_member("starting_value", - value, - "engine.util.attribute.AttributeSetting", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def ignore_armor_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the ignore armor effect (ID: 63). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def imperial_tech_id_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the imperial tech ID effect (ID: 24). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def kidnap_storage_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the kidnap storage effect (ID: 57). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def los_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the line of sight modify effect (ID: 1). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = f"{game_entity_name}.LineOfSight" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}LineOfSightWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}LineOfSight" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("range", - value, - "engine.ability.type.LineOfSight", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def max_projectiles_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the max projectiles modify effect (ID: 107). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = f"{game_entity_name}.Attack" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}MaxProjectilesWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}MaxProjectiles" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("max_projectiles", - value, - "engine.ability.type.ShootProjectile", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def min_projectiles_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the min projectiles modify effect (ID: 102). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = f"{game_entity_name}.Attack" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}MinProjectilesWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}MinProjectiles" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("min_projectiles", - value, - "engine.ability.type.ShootProjectile", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def max_range_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the max range modify effect (ID: 12). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - 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.Ranged" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - elif line.is_melee(): - if line.is_ranged(): - patch_target_ref = f"{game_entity_name}.Attack.Ranged" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - else: - # excludes ram upgrades - return patches - - elif line.has_command(104): - patch_target_ref = f"{game_entity_name}.Convert.Ranged" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - else: - # no matching ability - return patches - - # Wrapper - wrapper_name = f"Change{game_entity_name}MaxRangeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}MaxRange" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("max_range", - value, - patch_target_parent, - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def min_range_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the min range modify effect (ID: 20). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - 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.Ranged" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - elif line.is_melee(): - patch_target_ref = f"{game_entity_name}.Attack.Ranged" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - elif line.has_command(104): - patch_target_ref = f"{game_entity_name}.Convert.Ranged" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - else: - return [] - - # Wrapper - wrapper_name = f"Change{game_entity_name}MinRangeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}MinRange" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("min_range", - value, - patch_target_parent, - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def move_speed_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the move speed modify effect (ID: 5). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = f"{game_entity_name}.Move" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}MoveSpeedWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}MoveSpeed" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("speed", - value, - "engine.ability.type.Move", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def projectile_unit_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the projectile modify effect (ID: 16). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def reload_time_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the reload time modify effect (ID: 10). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if line.is_projectile_shooter(): - patch_target_ref = f"{game_entity_name}.Attack" - 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_forward_ref = ForwardRef(line, patch_target_ref) - patch_target_parent = "engine.ability.type.ApplyDiscreteEffect" - - elif line.has_command(104): - patch_target_ref = f"{game_entity_name}.Convert" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - patch_target_parent = "engine.ability.type.ApplyDiscreteEffect" - - else: - # No matching ability - return patches - - # Wrapper - wrapper_name = f"Change{game_entity_name}ReloadTimeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}ReloadTime" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("reload_time", - value, - patch_target_parent, - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def resource_cost_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the resource modify effect (ID: 100). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit = line.get_head_unit() - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - for resource_amount in head_unit["resource_cost"].value: - resource_id = resource_amount["type_id"].value - - resource_name = "" - if resource_id == -1: - # Not a valid resource - continue - - if resource_id == 0: - resource_name = "Food" - - elif resource_id == 1: - resource_name = "Wood" - - elif resource_id == 2: - resource_name = "Stone" - - elif resource_id == 3: - resource_name = "Gold" - - else: - # Other resource ids are handled differently - continue - - # Skip resources that are only expected to be there - if not resource_amount["enabled"].value: - continue - - patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}Cost.{resource_name}Amount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}{resource_name}CostWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}{resource_name}Cost" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def resource_storage_1_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the resource storage 1 modify effect (ID: 21). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - if line.is_harvestable(): - patch_target_ref = f"{game_entity_name}.Harvestable.{game_entity_name}ResourceSpot" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - wrapper_name = f"Change{game_entity_name}HarvestableAmountWrapper" - nyan_patch_name = f"Change{game_entity_name}HarvestableAmount" - - else: - patch_target_ref = f"{game_entity_name}.ProvideContingent.PopSpace" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - wrapper_name = f"Change{game_entity_name}PopSpaceWrapper" - nyan_patch_name = f"Change{game_entity_name}PopSpace" - - # Wrapper - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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 line.is_harvestable(): - nyan_patch_raw_api_object.add_raw_patch_member("max_amount", - value, - "engine.util.resource_spot.ResourceSpot", - operator) - - else: - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def rotation_speed_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the move speed modify effect (ID: 6). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def search_radius_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the search radius modify effect (ID: 23). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - if isinstance(line, GenieBuildingLineGroup) and not line.is_projectile_shooter(): - # Does not have the ability - return patches - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - stance_names = ["Aggressive", "Defensive", "StandGround", "Passive"] - - for stance_name in stance_names: - patch_target_ref = f"{game_entity_name}.GameEntityStance.{stance_name}" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}{stance_name}SearchRangeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}{stance_name}SearchRange" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("search_range", - value, - "engine.util.game_entity_stance.GameEntityStance", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def standing_wonders_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the standing wonders effect (ID: 42). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def tc_available_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the TC available effect (ID: 48). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def terrain_defense_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the terrain defense modify effect (ID: 18). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def train_button_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the train button modify effect (ID: 43). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def tribute_inefficiency_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the tribute inefficiency effect (ID: 46). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def unit_size_x_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the unit size x modify effect (ID: 3). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def unit_size_y_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the unit size y modify effect (ID: 4). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def work_rate_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the work rate modify effect (ID: 13). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches + accuracy_upgrade = staticmethod(accuracy_upgrade) + armor_upgrade = staticmethod(armor_upgrade) + attack_upgrade = staticmethod(attack_upgrade) + ballistics_upgrade = staticmethod(ballistics_upgrade) + attack_warning_sound_upgrade = staticmethod(attack_warning_sound_upgrade) + blast_radius_upgrade = staticmethod(blast_radius_upgrade) + carry_capacity_upgrade = staticmethod(carry_capacity_upgrade) + cost_food_upgrade = staticmethod(cost_food_upgrade) + cost_wood_upgrade = staticmethod(cost_wood_upgrade) + cost_gold_upgrade = staticmethod(cost_gold_upgrade) + cost_stone_upgrade = staticmethod(cost_stone_upgrade) + creation_time_upgrade = staticmethod(creation_time_upgrade) + garrison_capacity_upgrade = staticmethod(garrison_capacity_upgrade) + garrison_heal_upgrade = staticmethod(garrison_heal_upgrade) + graphics_angle_upgrade = staticmethod(graphics_angle_upgrade) + gold_counter_upgrade = staticmethod(gold_counter_upgrade) + hp_upgrade = staticmethod(health_upgrade) + ignore_armor_upgrade = staticmethod(ignore_armor_upgrade) + imperial_tech_id_upgrade = staticmethod(imperial_tech_id_upgrade) + kidnap_storage_upgrade = staticmethod(kidnap_storage_upgrade) + los_upgrade = staticmethod(line_of_sight_upgrade) + max_projectiles_upgrade = staticmethod(max_projectiles_upgrade) + min_projectiles_upgrade = staticmethod(min_projectiles_upgrade) + max_range_upgrade = staticmethod(max_range_upgrade) + min_range_upgrade = staticmethod(min_range_upgrade) + move_speed_upgrade = staticmethod(move_speed_upgrade) + projectile_unit_upgrade = staticmethod(projectile_unit_upgrade) + reload_time_upgrade = staticmethod(reload_time_upgrade) + resource_cost_upgrade = staticmethod(resource_cost_upgrade) + resource_storage_1_upgrade = staticmethod(resource_storage_1_upgrade) + rotation_speed_upgrade = staticmethod(rotation_speed_upgrade) + search_radius_upgrade = staticmethod(search_radius_upgrade) + standing_wonders_upgrade = staticmethod(standing_wonders_upgrade) + tc_available_upgrade = staticmethod(tc_available_upgrade) + terrain_defense_upgrade = staticmethod(terrain_defense_upgrade) + train_button_upgrade = staticmethod(train_button_upgrade) + tribute_inefficiency_upgrade = staticmethod(tribute_inefficiency_upgrade) + unit_size_x_upgrade = staticmethod(unit_size_x_upgrade) + unit_size_y_upgrade = staticmethod(unit_size_y_upgrade) + work_rate_upgrade = staticmethod(work_rate_upgrade) From d5adbd0b64059c40e0536aaa4c4fcb803e4d4a62 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 00:08:13 +0200 Subject: [PATCH 123/163] convert: Refactor AoCUpgradeResourceSubprocessor into separate files. --- .../processor/conversion/aoc/CMakeLists.txt | 1 + .../aoc/upgrade_resource/CMakeLists.txt | 30 + .../aoc/upgrade_resource/__init__.py | 5 + .../aoc/upgrade_resource/berserk_heal_rate.py | 104 ++ .../aoc/upgrade_resource/bonus_population.py | 96 + .../upgrade_resource/building_conversion.py | 180 ++ .../upgrade_resource/chinese_tech_discount.py | 34 + .../upgrade_resource/construction_speed.py | 34 + .../upgrade_resource/conversion_resistance.py | 76 + .../aoc/upgrade_resource/crenellations.py | 34 + .../upgrade_resource/faith_recharge_rate.py | 102 ++ .../aoc/upgrade_resource/farm_food.py | 102 ++ .../aoc/upgrade_resource/gather_efficiency.py | 97 + .../aoc/upgrade_resource/heal_range.py | 102 ++ .../aoc/upgrade_resource/heal_rate.py | 34 + .../aoc/upgrade_resource/herding_dominance.py | 34 + .../conversion/aoc/upgrade_resource/heresy.py | 34 + .../aoc/upgrade_resource/monk_conversion.py | 103 ++ .../aoc/upgrade_resource/relic_gold_bonus.py | 36 + .../aoc/upgrade_resource/research_time.py | 34 + .../aoc/upgrade_resource/reveal_ally.py | 34 + .../aoc/upgrade_resource/reveal_enemy.py | 34 + .../aoc/upgrade_resource/ship_conversion.py | 34 + .../aoc/upgrade_resource/siege_conversion.py | 34 + .../aoc/upgrade_resource/spies_discount.py | 34 + .../upgrade_resource/starting_resources.py | 201 +++ .../aoc/upgrade_resource/theocracy.py | 34 + .../aoc/upgrade_resource/trade_penalty.py | 34 + .../upgrade_resource/tribute_inefficiency.py | 34 + .../aoc/upgrade_resource/wonder_time.py | 34 + .../aoc/upgrade_resource_subprocessor.py | 1596 +---------------- 31 files changed, 1847 insertions(+), 1528 deletions(-) create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/__init__.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/berserk_heal_rate.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/bonus_population.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/building_conversion.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/chinese_tech_discount.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/construction_speed.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/conversion_resistance.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/crenellations.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/faith_recharge_rate.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/farm_food.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/gather_efficiency.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/heal_range.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/heal_rate.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/herding_dominance.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/heresy.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/monk_conversion.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/relic_gold_bonus.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/research_time.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/reveal_ally.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/reveal_enemy.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/ship_conversion.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/siege_conversion.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/spies_discount.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/starting_resources.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/theocracy.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/trade_penalty.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/tribute_inefficiency.py create mode 100644 openage/convert/processor/conversion/aoc/upgrade_resource/wonder_time.py diff --git a/openage/convert/processor/conversion/aoc/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index de12475174..5141dfba8e 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -33,3 +33,4 @@ add_subdirectory(upgrade_ability) add_subdirectory(upgrade_attribute) add_subdirectory(upgrade_effect) add_subdirectory(upgrade_resistance) +add_subdirectory(upgrade_resource) diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/CMakeLists.txt b/openage/convert/processor/conversion/aoc/upgrade_resource/CMakeLists.txt new file mode 100644 index 0000000000..0726a9d98e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/CMakeLists.txt @@ -0,0 +1,30 @@ +add_py_modules( + __init__.py + berserk_heal_rate.py + bonus_population.py + building_conversion.py + chinese_tech_discount.py + construction_speed.py + conversion_resistance.py + crenellations.py + faith_recharge_rate.py + farm_food.py + gather_efficiency.py + heal_range.py + heal_rate.py + herding_dominance.py + heresy.py + monk_conversion.py + relic_gold_bonus.py + research_time.py + reveal_ally.py + reveal_enemy.py + ship_conversion.py + siege_conversion.py + spies_discount.py + starting_resources.py + theocracy.py + trade_penalty.py + tribute_inefficiency.py + wonder_time.py +) diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/__init__.py b/openage/convert/processor/conversion/aoc/upgrade_resource/__init__.py new file mode 100644 index 0000000000..8863648306 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create upgrade patches for civ resources in AoC. +""" diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/berserk_heal_rate.py b/openage/convert/processor/conversion/aoc/upgrade_resource/berserk_heal_rate.py new file mode 100644 index 0000000000..cacd195180 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/berserk_heal_rate.py @@ -0,0 +1,104 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the berserk heal rate in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from ......nyan.nyan_structs import MemberOperator + + +def berserk_heal_rate_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the berserk heal rate modify effect (ID: 96). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + berserk_id = 692 + dataset = converter_group.data + line = dataset.unit_lines[berserk_id] + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[berserk_id][0] + + patch_target_ref = f"{game_entity_name}.RegenerateHealth.HealthRate" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}HealthRegenerationWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}HealthRegeneration" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + # Regeneration is on a counter, so we have to invert the value + value = 1 / value + nyan_patch_raw_api_object.add_raw_patch_member("rate", + value, + "engine.util.attribute.AttributeRate", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/bonus_population.py b/openage/convert/processor/conversion/aoc/upgrade_resource/bonus_population.py new file mode 100644 index 0000000000..42a01b6415 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/bonus_population.py @@ -0,0 +1,96 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for bonus population space in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from ......nyan.nyan_structs import MemberOperator + + +def bonus_population_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the bonus population effect (ID: 32). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + dataset = converter_group.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + patch_target_ref = "util.resource.types.PopulationSpace" + patch_target = dataset.pregen_nyan_objects[patch_target_ref].get_nyan_object() + + # Wrapper + wrapper_name = "ChangePopulationCapWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = "ChangePopulationCap" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("max_amount", + value, + "engine.util.resource.ResourceContingent", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/building_conversion.py b/openage/convert/processor/conversion/aoc/upgrade_resource/building_conversion.py new file mode 100644 index 0000000000..4719ce0e1c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/building_conversion.py @@ -0,0 +1,180 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for building conversion in AoC. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + + +def building_conversion_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the building conversion effect (ID: 28). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + monk_id = 125 + dataset = converter_group.data + line = dataset.unit_lines[monk_id] + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[monk_id][0] + + patch_target_ref = f"{game_entity_name}.Convert" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Building conversion + + # Wrapper + wrapper_name = "EnableBuildingConversionWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = "EnableBuildingConversion" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + # New allowed types + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object()] + nyan_patch_raw_api_object.add_raw_patch_member("allowed_types", + allowed_types, + "engine.ability.type.ApplyDiscreteEffect", + MemberOperator.ADD) + + # Blacklisted buildings + tc_line = dataset.building_lines[109] + farm_line = dataset.building_lines[50] + fish_trap_line = dataset.building_lines[199] + monastery_line = dataset.building_lines[104] + castle_line = dataset.building_lines[82] + palisade_line = dataset.building_lines[72] + stone_wall_line = dataset.building_lines[117] + stone_gate_line = dataset.building_lines[64] + wonder_line = dataset.building_lines[276] + + blacklisted_forward_refs = [ForwardRef(tc_line, "TownCenter"), + ForwardRef(farm_line, "Farm"), + ForwardRef(fish_trap_line, "FishingTrap"), + ForwardRef(monastery_line, "Monastery"), + ForwardRef(castle_line, "Castle"), + ForwardRef(palisade_line, "PalisadeWall"), + ForwardRef(stone_wall_line, "StoneWall"), + ForwardRef(stone_gate_line, "StoneGate"), + ForwardRef(wonder_line, "Wonder"), + ] + nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", + blacklisted_forward_refs, + "engine.ability.type.ApplyDiscreteEffect", + 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) + + # Siege unit conversion + + # Wrapper + wrapper_name = "EnableSiegeUnitConversionWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = "EnableSiegeUnitConversion" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + # Blacklisted units + blacklisted_entities = [] + for unit_line in dataset.unit_lines.values(): + if unit_line.get_class_id() in (13, 55): + # Siege units + blacklisted_name = name_lookup_dict[unit_line.get_head_unit_id()][0] + blacklisted_entities.append(ForwardRef(unit_line, blacklisted_name)) + + nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", + blacklisted_entities, + "engine.ability.type.ApplyDiscreteEffect", + MemberOperator.SUBTRACT) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/chinese_tech_discount.py b/openage/convert/processor/conversion/aoc/upgrade_resource/chinese_tech_discount.py new file mode 100644 index 0000000000..ea3a36a28d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/chinese_tech_discount.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the chinese tech discount in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def chinese_tech_discount_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the chinese tech discount effect (ID: 85). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/construction_speed.py b/openage/convert/processor/conversion/aoc/upgrade_resource/construction_speed.py new file mode 100644 index 0000000000..1c55369567 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/construction_speed.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for construction speed in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def construction_speed_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the construction speed modify effect (ID: 195). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/conversion_resistance.py b/openage/convert/processor/conversion/aoc/upgrade_resource/conversion_resistance.py new file mode 100644 index 0000000000..a87bf81a3b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/conversion_resistance.py @@ -0,0 +1,76 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for conversion resistances in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def conversion_resistance_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion resistance modify effect (ID: 77). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def conversion_resistance_min_rounds_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion resistance modify effect (ID: 178). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def conversion_resistance_max_rounds_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion resistance modify effect (ID: 179). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/crenellations.py b/openage/convert/processor/conversion/aoc/upgrade_resource/crenellations.py new file mode 100644 index 0000000000..12380c2fc8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/crenellations.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the crenellations upgrade in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def crenellations_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the crenellations effect (ID: 194). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/faith_recharge_rate.py b/openage/convert/processor/conversion/aoc/upgrade_resource/faith_recharge_rate.py new file mode 100644 index 0000000000..d324b1f91b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/faith_recharge_rate.py @@ -0,0 +1,102 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the faith recharge rate in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from ......nyan.nyan_structs import MemberOperator + + +def faith_recharge_rate_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the faith_recharge_rate modify effect (ID: 35). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + monk_id = 125 + dataset = converter_group.data + line = dataset.unit_lines[monk_id] + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[monk_id][0] + + patch_target_ref = f"{game_entity_name}.RegenerateFaith.FaithRate" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}FaithRegenerationWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}FaithRegeneration" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("rate", + value, + "engine.util.attribute.AttributeRate", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/farm_food.py b/openage/convert/processor/conversion/aoc/upgrade_resource/farm_food.py new file mode 100644 index 0000000000..4131b01ea3 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/farm_food.py @@ -0,0 +1,102 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for farm food capacity in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from ......nyan.nyan_structs import MemberOperator + + +def farm_food_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the farm food modify effect (ID: 36). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + farm_id = 50 + dataset = converter_group.data + line = dataset.building_lines[farm_id] + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[farm_id][0] + + patch_target_ref = f"{game_entity_name}.Harvestable.{game_entity_name}ResourceSpot" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}FoodAmountWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}FoodAmount" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("max_amount", + value, + "engine.util.resource_spot.ResourceSpot", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/gather_efficiency.py b/openage/convert/processor/conversion/aoc/upgrade_resource/gather_efficiency.py new file mode 100644 index 0000000000..7e12e89d93 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/gather_efficiency.py @@ -0,0 +1,97 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for gather efficiency modifiers in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def gather_food_efficiency_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the food gathering efficiency modify effect (ID: 190). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def gather_wood_efficiency_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the wood gathering efficiency modify effect (ID: 189). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def gather_gold_efficiency_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the gold gathering efficiency modify effect (ID: 47). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def gather_stone_efficiency_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the stone gathering efficiency modify effect (ID: 79). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/heal_range.py b/openage/convert/processor/conversion/aoc/upgrade_resource/heal_range.py new file mode 100644 index 0000000000..0d4c22cd05 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/heal_range.py @@ -0,0 +1,102 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for heal range in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from ......nyan.nyan_structs import MemberOperator + + +def heal_range_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the heal range modify effect (ID: 90). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + monk_id = 125 + dataset = converter_group.data + line = dataset.unit_lines[monk_id] + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[monk_id][0] + + patch_target_ref = f"{game_entity_name}.Heal.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}HealRangeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}HealRange" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("max_range", + value, + "engine.ability.property.type.Ranged", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/heal_rate.py b/openage/convert/processor/conversion/aoc/upgrade_resource/heal_rate.py new file mode 100644 index 0000000000..fc7ffe5af5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/heal_rate.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for heal rates in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def heal_rate_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the heal rate modify effect (ID: 89). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/herding_dominance.py b/openage/convert/processor/conversion/aoc/upgrade_resource/herding_dominance.py new file mode 100644 index 0000000000..34f481cc5b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/herding_dominance.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for herding dominance in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def herding_dominance_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the herding dominance effect (ID: 97). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/heresy.py b/openage/convert/processor/conversion/aoc/upgrade_resource/heresy.py new file mode 100644 index 0000000000..b2ced5600d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/heresy.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the heresy upgrade in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def heresy_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the heresy effect (ID: 192). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/monk_conversion.py b/openage/convert/processor/conversion/aoc/upgrade_resource/monk_conversion.py new file mode 100644 index 0000000000..994f579a6b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/monk_conversion.py @@ -0,0 +1,103 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the monk conversion in AoC. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + + +def monk_conversion_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the monk conversion effect (ID: 27). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + monk_id = 125 + dataset = converter_group.data + line = dataset.unit_lines[monk_id] + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[monk_id][0] + + patch_target_ref = f"{game_entity_name}.Convert" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Enable{game_entity_name}ConversionWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Enable{game_entity_name}Conversion" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + monk_forward_ref = ForwardRef(line, game_entity_name) + nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", + [monk_forward_ref], + "engine.ability.type.ApplyDiscreteEffect", + MemberOperator.SUBTRACT) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/relic_gold_bonus.py b/openage/convert/processor/conversion/aoc/upgrade_resource/relic_gold_bonus.py new file mode 100644 index 0000000000..213314cd69 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/relic_gold_bonus.py @@ -0,0 +1,36 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the relic gold bonus in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def relic_gold_bonus_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the relic gold bonus modify effect (ID: 191). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: Any + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/research_time.py b/openage/convert/processor/conversion/aoc/upgrade_resource/research_time.py new file mode 100644 index 0000000000..2f31b8cde1 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/research_time.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for research time in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def research_time_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the research time modify effect (ID: 86). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/reveal_ally.py b/openage/convert/processor/conversion/aoc/upgrade_resource/reveal_ally.py new file mode 100644 index 0000000000..c5da17c25b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/reveal_ally.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the reveal ally upgrade in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def reveal_ally_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the reveal ally modify effect (ID: 50). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/reveal_enemy.py b/openage/convert/processor/conversion/aoc/upgrade_resource/reveal_enemy.py new file mode 100644 index 0000000000..fa5d4b31f7 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/reveal_enemy.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the reveal enemy upgrade in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def reveal_enemy_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the reveal enemy modify effect (ID: 183). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/ship_conversion.py b/openage/convert/processor/conversion/aoc/upgrade_resource/ship_conversion.py new file mode 100644 index 0000000000..e28038d5d5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/ship_conversion.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for ship conversion in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def ship_conversion_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the ship conversion effect (ID: 87). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused in AoC + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/siege_conversion.py b/openage/convert/processor/conversion/aoc/upgrade_resource/siege_conversion.py new file mode 100644 index 0000000000..d8989ef813 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/siege_conversion.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for siege conversion in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def siege_conversion_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the siege conversion effect (ID: 29). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/spies_discount.py b/openage/convert/processor/conversion/aoc/upgrade_resource/spies_discount.py new file mode 100644 index 0000000000..259f4377fc --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/spies_discount.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the spies upgrade discount in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def spies_discount_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the spies discount effect (ID: 197). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/starting_resources.py b/openage/convert/processor/conversion/aoc/upgrade_resource/starting_resources.py new file mode 100644 index 0000000000..1f27b5244a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/starting_resources.py @@ -0,0 +1,201 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for starting resources in AoC. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from ......nyan.nyan_structs import MemberOperator + + +def starting_food_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the starting food modify effect (ID: 91). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def starting_wood_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the starting wood modify effect (ID: 92). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def starting_stone_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the starting stone modify effect (ID: 93). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def starting_gold_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the starting gold modify effect (ID: 94). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def starting_villagers_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the starting villagers modify effect (ID: 84). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def starting_population_space_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the starting popspace modify effect (ID: 4). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + dataset = converter_group.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + patch_target_ref = "util.resource.types.PopulationSpace" + patch_target = dataset.pregen_nyan_objects[patch_target_ref].get_nyan_object() + + # Wrapper + wrapper_name = "ChangeInitialPopulationLimitWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = "ChangeInitialPopulationLimit" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("min_amount", + value, + "engine.util.resource.ResourceContingent", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/theocracy.py b/openage/convert/processor/conversion/aoc/upgrade_resource/theocracy.py new file mode 100644 index 0000000000..a178b27191 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/theocracy.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the theocracy upgrade in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def theocracy_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the theocracy effect (ID: 193). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/trade_penalty.py b/openage/convert/processor/conversion/aoc/upgrade_resource/trade_penalty.py new file mode 100644 index 0000000000..062c572946 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/trade_penalty.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for trade penalties in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def trade_penalty_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the trade penalty modify effect (ID: 78). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/tribute_inefficiency.py b/openage/convert/processor/conversion/aoc/upgrade_resource/tribute_inefficiency.py new file mode 100644 index 0000000000..8d100a9c4b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/tribute_inefficiency.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for tribute inefficiency in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def tribute_inefficiency_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the tribute inefficiency modify effect (ID: 46). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource/wonder_time.py b/openage/convert/processor/conversion/aoc/upgrade_resource/wonder_time.py new file mode 100644 index 0000000000..32bfef5b79 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/upgrade_resource/wonder_time.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for wonder time limits in AoC. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def wonder_time_increase_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the wonder time modify effect (ID: 196). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/aoc/upgrade_resource_subprocessor.py b/openage/convert/processor/conversion/aoc/upgrade_resource_subprocessor.py index 5bcb5abb7b..44629c3d69 100644 --- a/openage/convert/processor/conversion/aoc/upgrade_resource_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/upgrade_resource_subprocessor.py @@ -1,25 +1,39 @@ # 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 -# -# TODO: Remove when all methods are implemented -# pylint: disable=unused-argument,line-too-long """ Creates upgrade patches for resource modification effects in AoC. """ -from __future__ import annotations -import typing - -from .....nyan.nyan_structs import MemberOperator -from ....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.nyan.nyan_structs import MemberOperator +from .upgrade_resource.berserk_heal_rate import berserk_heal_rate_upgrade +from .upgrade_resource.bonus_population import bonus_population_upgrade +from .upgrade_resource.building_conversion import building_conversion_upgrade +from .upgrade_resource.chinese_tech_discount import chinese_tech_discount_upgrade +from .upgrade_resource.construction_speed import construction_speed_upgrade +from .upgrade_resource.conversion_resistance import conversion_resistance_upgrade, \ + conversion_resistance_min_rounds_upgrade, conversion_resistance_max_rounds_upgrade +from .upgrade_resource.crenellations import crenellations_upgrade +from .upgrade_resource.faith_recharge_rate import faith_recharge_rate_upgrade +from .upgrade_resource.farm_food import farm_food_upgrade +from .upgrade_resource.gather_efficiency import gather_food_efficiency_upgrade, \ + gather_wood_efficiency_upgrade, gather_gold_efficiency_upgrade, gather_stone_efficiency_upgrade +from .upgrade_resource.heal_range import heal_range_upgrade +from .upgrade_resource.heal_rate import heal_rate_upgrade +from .upgrade_resource.herding_dominance import herding_dominance_upgrade +from .upgrade_resource.heresy import heresy_upgrade +from .upgrade_resource.monk_conversion import monk_conversion_upgrade +from .upgrade_resource.relic_gold_bonus import relic_gold_bonus_upgrade +from .upgrade_resource.research_time import research_time_upgrade +from .upgrade_resource.reveal_ally import reveal_ally_upgrade +from .upgrade_resource.reveal_enemy import reveal_enemy_upgrade +from .upgrade_resource.siege_conversion import siege_conversion_upgrade +from .upgrade_resource.ship_conversion import ship_conversion_upgrade +from .upgrade_resource.spies_discount import spies_discount_upgrade +from .upgrade_resource.starting_resources import starting_food_upgrade, \ + starting_wood_upgrade, starting_stone_upgrade, starting_gold_upgrade, \ + starting_villagers_upgrade, starting_population_space_upgrade +from .upgrade_resource.theocracy import theocracy_upgrade +from .upgrade_resource.trade_penalty import trade_penalty_upgrade +from .upgrade_resource.tribute_inefficiency import tribute_inefficiency_upgrade +from .upgrade_resource.wonder_time import wonder_time_increase_upgrade class AoCUpgradeResourceSubprocessor: @@ -27,1514 +41,40 @@ class AoCUpgradeResourceSubprocessor: Creates raw API objects for resource upgrade effects in AoC. """ - @staticmethod - def berserk_heal_rate_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the berserk heal rate modify effect (ID: 96). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - berserk_id = 692 - dataset = converter_group.data - line = dataset.unit_lines[berserk_id] - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[berserk_id][0] - - patch_target_ref = f"{game_entity_name}.RegenerateHealth.HealthRate" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}HealthRegenerationWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}HealthRegeneration" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - # Regeneration is on a counter, so we have to invert the value - value = 1 / value - nyan_patch_raw_api_object.add_raw_patch_member("rate", - value, - "engine.util.attribute.AttributeRate", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def bonus_population_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the bonus population effect (ID: 32). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - dataset = converter_group.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - patch_target_ref = "util.resource.types.PopulationSpace" - patch_target = dataset.pregen_nyan_objects[patch_target_ref].get_nyan_object() - - # Wrapper - wrapper_name = "ChangePopulationCapWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = "ChangePopulationCap" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("max_amount", - value, - "engine.util.resource.ResourceContingent", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def building_conversion_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the building conversion effect (ID: 28). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - monk_id = 125 - dataset = converter_group.data - line = dataset.unit_lines[monk_id] - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[monk_id][0] - - patch_target_ref = f"{game_entity_name}.Convert" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Building conversion - - # Wrapper - wrapper_name = "EnableBuildingConversionWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = "EnableBuildingConversion" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - # New allowed types - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object()] - nyan_patch_raw_api_object.add_raw_patch_member("allowed_types", - allowed_types, - "engine.ability.type.ApplyDiscreteEffect", - MemberOperator.ADD) - - # Blacklisted buildings - tc_line = dataset.building_lines[109] - farm_line = dataset.building_lines[50] - fish_trap_line = dataset.building_lines[199] - monastery_line = dataset.building_lines[104] - castle_line = dataset.building_lines[82] - palisade_line = dataset.building_lines[72] - stone_wall_line = dataset.building_lines[117] - stone_gate_line = dataset.building_lines[64] - wonder_line = dataset.building_lines[276] - - blacklisted_forward_refs = [ForwardRef(tc_line, "TownCenter"), - ForwardRef(farm_line, "Farm"), - ForwardRef(fish_trap_line, "FishingTrap"), - ForwardRef(monastery_line, "Monastery"), - ForwardRef(castle_line, "Castle"), - ForwardRef(palisade_line, "PalisadeWall"), - ForwardRef(stone_wall_line, "StoneWall"), - ForwardRef(stone_gate_line, "StoneGate"), - ForwardRef(wonder_line, "Wonder"), - ] - nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", - blacklisted_forward_refs, - "engine.ability.type.ApplyDiscreteEffect", - 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) - - # Siege unit conversion - - # Wrapper - wrapper_name = "EnableSiegeUnitConversionWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = "EnableSiegeUnitConversion" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - # Blacklisted units - blacklisted_entities = [] - for unit_line in dataset.unit_lines.values(): - if unit_line.get_class_id() in (13, 55): - # Siege units - blacklisted_name = name_lookup_dict[unit_line.get_head_unit_id()][0] - blacklisted_entities.append(ForwardRef(unit_line, blacklisted_name)) - - nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", - blacklisted_entities, - "engine.ability.type.ApplyDiscreteEffect", - MemberOperator.SUBTRACT) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def chinese_tech_discount_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the chinese tech discount effect (ID: 85). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def construction_speed_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the construction speed modify effect (ID: 195). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_resistance_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion resistance modify effect (ID: 77). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_resistance_min_rounds_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion resistance modify effect (ID: 178). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_resistance_max_rounds_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion resistance modify effect (ID: 179). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def crenellations_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the crenellations effect (ID: 194). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def faith_recharge_rate_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the faith_recharge_rate modify effect (ID: 35). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - monk_id = 125 - dataset = converter_group.data - line = dataset.unit_lines[monk_id] - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[monk_id][0] - - patch_target_ref = f"{game_entity_name}.RegenerateFaith.FaithRate" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}FaithRegenerationWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}FaithRegeneration" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("rate", - value, - "engine.util.attribute.AttributeRate", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def farm_food_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the farm food modify effect (ID: 36). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - farm_id = 50 - dataset = converter_group.data - line = dataset.building_lines[farm_id] - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[farm_id][0] - - patch_target_ref = f"{game_entity_name}.Harvestable.{game_entity_name}ResourceSpot" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}FoodAmountWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}FoodAmount" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("max_amount", - value, - "engine.util.resource_spot.ResourceSpot", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def gather_food_efficiency_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the food gathering efficiency modify effect (ID: 190). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def gather_wood_efficiency_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the wood gathering efficiency modify effect (ID: 189). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def gather_gold_efficiency_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the gold gathering efficiency modify effect (ID: 47). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def gather_stone_efficiency_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the stone gathering efficiency modify effect (ID: 79). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def heal_range_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the heal range modify effect (ID: 90). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - monk_id = 125 - dataset = converter_group.data - line = dataset.unit_lines[monk_id] - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[monk_id][0] - - patch_target_ref = f"{game_entity_name}.Heal.Ranged" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}HealRangeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}HealRange" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("max_range", - value, - "engine.ability.property.type.Ranged", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def heal_rate_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the heal rate modify effect (ID: 89). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def herding_dominance_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the herding dominance effect (ID: 97). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def heresy_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the heresy effect (ID: 192). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def monk_conversion_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the monk conversion effect (ID: 27). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - monk_id = 125 - dataset = converter_group.data - line = dataset.unit_lines[monk_id] - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[monk_id][0] - - patch_target_ref = f"{game_entity_name}.Convert" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Enable{game_entity_name}ConversionWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Enable{game_entity_name}Conversion" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - monk_forward_ref = ForwardRef(line, game_entity_name) - nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", - [monk_forward_ref], - "engine.ability.type.ApplyDiscreteEffect", - MemberOperator.SUBTRACT) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def relic_gold_bonus_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the relic gold bonus modify effect (ID: 191). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - return patches - - @staticmethod - def research_time_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the research time modify effect (ID: 86). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def reveal_ally_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the reveal ally modify effect (ID: 50). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def reveal_enemy_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the reveal enemy modify effect (ID: 183). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def siege_conversion_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the siege conversion effect (ID: 29). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO - - return patches - - @staticmethod - def ship_conversion_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the ship conversion effect (ID: 87). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused in AoC - - return patches - - @staticmethod - def spies_discount_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the spies discount effect (ID: 197). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def starting_food_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the starting food modify effect (ID: 91). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def starting_wood_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the starting wood modify effect (ID: 92). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def starting_stone_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the starting stone modify effect (ID: 93). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def starting_gold_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the starting gold modify effect (ID: 94). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def starting_villagers_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the starting villagers modify effect (ID: 84). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def starting_population_space_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the starting popspace modify effect (ID: 4). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - dataset = converter_group.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - patch_target_ref = "util.resource.types.PopulationSpace" - patch_target = dataset.pregen_nyan_objects[patch_target_ref].get_nyan_object() - - # Wrapper - wrapper_name = "ChangeInitialPopulationLimitWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = "ChangeInitialPopulationLimit" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("min_amount", - value, - "engine.util.resource.ResourceContingent", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def theocracy_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the theocracy effect (ID: 193). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def trade_penalty_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the trade penalty modify effect (ID: 78). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def tribute_inefficiency_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the tribute inefficiency modify effect (ID: 46). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def wonder_time_increase_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the wonder time modify effect (ID: 196). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches + berserk_heal_rate_upgrade = staticmethod(berserk_heal_rate_upgrade) + bonus_population_upgrade = staticmethod(bonus_population_upgrade) + building_conversion_upgrade = staticmethod(building_conversion_upgrade) + chinese_tech_discount_upgrade = staticmethod(chinese_tech_discount_upgrade) + construction_speed_upgrade = staticmethod(construction_speed_upgrade) + conversion_resistance_upgrade = staticmethod(conversion_resistance_upgrade) + conversion_resistance_min_rounds_upgrade = staticmethod(conversion_resistance_min_rounds_upgrade) + conversion_resistance_max_rounds_upgrade = staticmethod(conversion_resistance_max_rounds_upgrade) + crenellations_upgrade = staticmethod(crenellations_upgrade) + faith_recharge_rate_upgrade = staticmethod(faith_recharge_rate_upgrade) + farm_food_upgrade = staticmethod(farm_food_upgrade) + gather_food_efficiency_upgrade = staticmethod(gather_food_efficiency_upgrade) + gather_wood_efficiency_upgrade = staticmethod(gather_wood_efficiency_upgrade) + gather_gold_efficiency_upgrade = staticmethod(gather_gold_efficiency_upgrade) + gather_stone_efficiency_upgrade = staticmethod(gather_stone_efficiency_upgrade) + heal_range_upgrade = staticmethod(heal_range_upgrade) + heal_rate_upgrade = staticmethod(heal_rate_upgrade) + herding_dominance_upgrade = staticmethod(herding_dominance_upgrade) + heresy_upgrade = staticmethod(heresy_upgrade) + monk_conversion_upgrade = staticmethod(monk_conversion_upgrade) + relic_gold_bonus_upgrade = staticmethod(relic_gold_bonus_upgrade) + research_time_upgrade = staticmethod(research_time_upgrade) + reveal_ally_upgrade = staticmethod(reveal_ally_upgrade) + reveal_enemy_upgrade = staticmethod(reveal_enemy_upgrade) + siege_conversion_upgrade = staticmethod(siege_conversion_upgrade) + ship_conversion_upgrade = staticmethod(ship_conversion_upgrade) + spies_discount_upgrade = staticmethod(spies_discount_upgrade) + starting_food_upgrade = staticmethod(starting_food_upgrade) + starting_wood_upgrade = staticmethod(starting_wood_upgrade) + starting_stone_upgrade = staticmethod(starting_stone_upgrade) + starting_gold_upgrade = staticmethod(starting_gold_upgrade) + starting_villagers_upgrade = staticmethod(starting_villagers_upgrade) + starting_population_space_upgrade = staticmethod(starting_population_space_upgrade) + theocracy_upgrade = staticmethod(theocracy_upgrade) + trade_penalty_upgrade = staticmethod(trade_penalty_upgrade) + tribute_inefficiency_upgrade = staticmethod(tribute_inefficiency_upgrade) + wonder_time_increase_upgrade = staticmethod(wonder_time_increase_upgrade) From f35ab3293f598f9b8b6841bad6cfab401ca7abfe Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 13:45:35 +0200 Subject: [PATCH 124/163] convert: Refactor DE1 processor classes into separate files. --- .../processor/conversion/de1/CMakeLists.txt | 3 + .../conversion/de1/main/CMakeLists.txt | 5 + .../processor/conversion/de1/main/__init__.py | 5 + .../de1/main/extract/CMakeLists.txt | 4 + .../conversion/de1/main/extract/__init__.py | 5 + .../conversion/de1/main/extract/graphics.py | 51 ++++++++ .../conversion/de1/media/CMakeLists.txt | 4 + .../conversion/de1/media/__init__.py | 5 + .../conversion/de1/media/graphics.py | 109 ++++++++++++++++++ .../conversion/de1/media_subprocessor.py | 101 +--------------- .../processor/conversion/de1/processor.py | 59 ++-------- 11 files changed, 202 insertions(+), 149 deletions(-) create mode 100644 openage/convert/processor/conversion/de1/main/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de1/main/__init__.py create mode 100644 openage/convert/processor/conversion/de1/main/extract/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de1/main/extract/__init__.py create mode 100644 openage/convert/processor/conversion/de1/main/extract/graphics.py create mode 100644 openage/convert/processor/conversion/de1/media/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de1/media/__init__.py create mode 100644 openage/convert/processor/conversion/de1/media/graphics.py diff --git a/openage/convert/processor/conversion/de1/CMakeLists.txt b/openage/convert/processor/conversion/de1/CMakeLists.txt index c2fb63366c..b384d38ee5 100644 --- a/openage/convert/processor/conversion/de1/CMakeLists.txt +++ b/openage/convert/processor/conversion/de1/CMakeLists.txt @@ -4,3 +4,6 @@ add_py_modules( modpack_subprocessor.py processor.py ) + +add_subdirectory(main) +add_subdirectory(media) diff --git a/openage/convert/processor/conversion/de1/main/CMakeLists.txt b/openage/convert/processor/conversion/de1/main/CMakeLists.txt new file mode 100644 index 0000000000..b7aa757e4d --- /dev/null +++ b/openage/convert/processor/conversion/de1/main/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py +) + +add_subdirectory(extract) diff --git a/openage/convert/processor/conversion/de1/main/__init__.py b/openage/convert/processor/conversion/de1/main/__init__.py new file mode 100644 index 0000000000..9e000c8162 --- /dev/null +++ b/openage/convert/processor/conversion/de1/main/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Routines for the main DE1 conversion process. +""" diff --git a/openage/convert/processor/conversion/de1/main/extract/CMakeLists.txt b/openage/convert/processor/conversion/de1/main/extract/CMakeLists.txt new file mode 100644 index 0000000000..a0d11293f2 --- /dev/null +++ b/openage/convert/processor/conversion/de1/main/extract/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + graphics.py +) diff --git a/openage/convert/processor/conversion/de1/main/extract/__init__.py b/openage/convert/processor/conversion/de1/main/extract/__init__.py new file mode 100644 index 0000000000..b88655a0c8 --- /dev/null +++ b/openage/convert/processor/conversion/de1/main/extract/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract AoC data from the game dataset and prepares it for conversion. +""" diff --git a/openage/convert/processor/conversion/de1/main/extract/graphics.py b/openage/convert/processor/conversion/de1/main/extract/graphics.py new file mode 100644 index 0000000000..cdec2609c9 --- /dev/null +++ b/openage/convert/processor/conversion/de1/main/extract/graphics.py @@ -0,0 +1,51 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract graphics from the DE1 data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_graphic import GenieGraphic +from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + +if typing.TYPE_CHECKING: + from ......value_object.read.value_members import ArrayMember + + +def extract_genie_graphics(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract graphic definitions from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + # call hierarchy: wrapper[0]->graphics + raw_graphics = gamespec[0]["graphics"].value + + for raw_graphic in raw_graphics: + # Can be ignored if there is no filename associated + filename = raw_graphic["filename"].value + if not filename: + continue + + # DE1 stores most graphics filenames as 'whatever_' + # where '' must be replaced by x1, x2 or x4 + # which corresponds to the graphics resolution variant + # + # we look for the x1 variant + if filename.endswith(""): + filename = f"{filename[:-4]}x1" + + graphic_id = raw_graphic["graphic_id"].value + graphic_members = raw_graphic.value + + graphic = GenieGraphic(graphic_id, full_data_set, members=graphic_members) + if filename.lower() not in full_data_set.existing_graphics: + graphic.exists = False + + full_data_set.genie_graphics.update({graphic.get_id(): graphic}) + + # Detect subgraphics + for genie_graphic in full_data_set.genie_graphics.values(): + genie_graphic.detect_subgraphics() diff --git a/openage/convert/processor/conversion/de1/media/CMakeLists.txt b/openage/convert/processor/conversion/de1/media/CMakeLists.txt new file mode 100644 index 0000000000..a0d11293f2 --- /dev/null +++ b/openage/convert/processor/conversion/de1/media/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + graphics.py +) diff --git a/openage/convert/processor/conversion/de1/media/__init__.py b/openage/convert/processor/conversion/de1/media/__init__.py new file mode 100644 index 0000000000..d060e03e03 --- /dev/null +++ b/openage/convert/processor/conversion/de1/media/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create media export requests for media files in the DE1 data. +""" diff --git a/openage/convert/processor/conversion/de1/media/graphics.py b/openage/convert/processor/conversion/de1/media/graphics.py new file mode 100644 index 0000000000..5a9dc48def --- /dev/null +++ b/openage/convert/processor/conversion/de1/media/graphics.py @@ -0,0 +1,109 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create export requests for graphics files. +""" + +from __future__ import annotations +import typing + +from .....entity_object.export.formats.sprite_metadata import LayerMode +from .....entity_object.export.media_export_request import MediaExportRequest +from .....entity_object.export.metadata_export import SpriteMetadataExport +from .....entity_object.export.metadata_export import TextureMetadataExport +from .....value_object.read.media_types import MediaType + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: + """ + Create export requests for graphics referenced by CombinedSprite objects. + """ + combined_sprites = full_data_set.combined_sprites.values() + handled_graphic_ids = set() + + for sprite in combined_sprites: + ref_graphics = sprite.get_graphics() + graphic_targetdirs = sprite.resolve_graphics_location() + + # Animation metadata file definiton + sprite_meta_filename = f"{sprite.get_filename()}.sprite" + sprite_meta_export = SpriteMetadataExport(sprite.resolve_sprite_location(), + sprite_meta_filename) + full_data_set.metadata_exports.append(sprite_meta_export) + + for graphic in ref_graphics: + graphic_id = graphic.get_id() + if graphic_id in handled_graphic_ids: + continue + + targetdir = graphic_targetdirs[graphic_id] + + # DE1 stores most graphics filenames as 'whatever_' + # where '' must be replaced by x1, x2 or x4 + # which corresponds to the graphics resolution variant + source_str = graphic['filename'].value[:-4] + + # TODO: Also convert x2 and x4 variants + source_filename = f"{source_str}x1.slp" + target_filename = f"{sprite.get_filename()}_{str(graphic['slp_id'].value)}.png" + + export_request = MediaExportRequest(MediaType.GRAPHICS, + targetdir, + source_filename, + target_filename) + full_data_set.graphics_exports.update({graphic_id: export_request}) + + # Texture metadata file definiton + # Same file stem as the image file and same targetdir + texture_meta_filename = f"{target_filename[:-4]}.texture" + texture_meta_export = TextureMetadataExport(targetdir, + texture_meta_filename) + full_data_set.metadata_exports.append(texture_meta_export) + + # Add texture image filename to texture metadata + texture_meta_export.add_imagefile(target_filename) + + # Add metadata from graphics to animation metadata + sequence_type = graphic["sequence_type"].value + if sequence_type == 0x00: + layer_mode = LayerMode.OFF + + elif sequence_type & 0x08: + layer_mode = LayerMode.ONCE + + else: + layer_mode = LayerMode.LOOP + + layer_pos = graphic["layer"].value + frame_rate = round(graphic["frame_rate"].value, ndigits=6) + if frame_rate < 0.000001: + frame_rate = None + + replay_delay = round(graphic["replay_delay"].value, ndigits=6) + if replay_delay < 0.000001: + replay_delay = None + + frame_count = graphic["frame_count"].value + angle_count = graphic["angle_count"].value + # mirror_mode = graphic["mirroring_mode"].value + sprite_meta_export.add_graphics_metadata(target_filename, + texture_meta_filename, + layer_mode, + layer_pos, + frame_rate, + replay_delay, + frame_count, + angle_count, + mirror_mode=0, + start_angle=270) + + # Notify metadata export about SLP metadata when the file is exported + export_request.add_observer(texture_meta_export) + export_request.add_observer(sprite_meta_export) + + handled_graphic_ids.add(graphic_id) + + # TODO: Terrains diff --git a/openage/convert/processor/conversion/de1/media_subprocessor.py b/openage/convert/processor/conversion/de1/media_subprocessor.py index b1a4f8ece1..d84f1cb194 100644 --- a/openage/convert/processor/conversion/de1/media_subprocessor.py +++ b/openage/convert/processor/conversion/de1/media_subprocessor.py @@ -1,6 +1,5 @@ # Copyright 2020-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals + """ Convert media information to metadata definitions and export requests. Subroutine of the main DE1 processor. @@ -8,12 +7,8 @@ from __future__ import annotations import typing -from ....entity_object.export.formats.sprite_metadata import LayerMode -from ....entity_object.export.media_export_request import MediaExportRequest -from ....entity_object.export.metadata_export import SpriteMetadataExport -from ....entity_object.export.metadata_export import TextureMetadataExport -from ....value_object.read.media_types import MediaType from ..aoc.media_subprocessor import AoCMediaSubprocessor +from .media.graphics import create_graphics_requests if typing.TYPE_CHECKING: from openage.convert.entity_object.conversion.aoc.genie_object_container\ @@ -33,94 +28,4 @@ def convert(cls, full_data_set: GenieObjectContainer) -> None: cls.create_graphics_requests(full_data_set) AoCMediaSubprocessor.create_sound_requests(full_data_set) - @staticmethod - def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: - """ - Create export requests for graphics referenced by CombinedSprite objects. - """ - combined_sprites = full_data_set.combined_sprites.values() - handled_graphic_ids = set() - - for sprite in combined_sprites: - ref_graphics = sprite.get_graphics() - graphic_targetdirs = sprite.resolve_graphics_location() - - # Animation metadata file definiton - sprite_meta_filename = f"{sprite.get_filename()}.sprite" - sprite_meta_export = SpriteMetadataExport(sprite.resolve_sprite_location(), - sprite_meta_filename) - full_data_set.metadata_exports.append(sprite_meta_export) - - for graphic in ref_graphics: - graphic_id = graphic.get_id() - if graphic_id in handled_graphic_ids: - continue - - targetdir = graphic_targetdirs[graphic_id] - - # DE1 stores most graphics filenames as 'whatever_' - # where '' must be replaced by x1, x2 or x4 - # which corresponds to the graphics resolution variant - source_str = graphic['filename'].value[:-4] - - # TODO: Also convert x2 and x4 variants - source_filename = f"{source_str}x1.slp" - target_filename = f"{sprite.get_filename()}_{str(graphic['slp_id'].value)}.png" - - export_request = MediaExportRequest(MediaType.GRAPHICS, - targetdir, - source_filename, - target_filename) - full_data_set.graphics_exports.update({graphic_id: export_request}) - - # Texture metadata file definiton - # Same file stem as the image file and same targetdir - texture_meta_filename = f"{target_filename[:-4]}.texture" - texture_meta_export = TextureMetadataExport(targetdir, - texture_meta_filename) - full_data_set.metadata_exports.append(texture_meta_export) - - # Add texture image filename to texture metadata - texture_meta_export.add_imagefile(target_filename) - - # Add metadata from graphics to animation metadata - sequence_type = graphic["sequence_type"].value - if sequence_type == 0x00: - layer_mode = LayerMode.OFF - - elif sequence_type & 0x08: - layer_mode = LayerMode.ONCE - - else: - layer_mode = LayerMode.LOOP - - layer_pos = graphic["layer"].value - frame_rate = round(graphic["frame_rate"].value, ndigits=6) - if frame_rate < 0.000001: - frame_rate = None - - replay_delay = round(graphic["replay_delay"].value, ndigits=6) - if replay_delay < 0.000001: - replay_delay = None - - frame_count = graphic["frame_count"].value - angle_count = graphic["angle_count"].value - # mirror_mode = graphic["mirroring_mode"].value - sprite_meta_export.add_graphics_metadata(target_filename, - texture_meta_filename, - layer_mode, - layer_pos, - frame_rate, - replay_delay, - frame_count, - angle_count, - mirror_mode=0, - start_angle=270) - - # Notify metadata export about SLP metadata when the file is exported - export_request.add_observer(texture_meta_export) - export_request.add_observer(sprite_meta_export) - - handled_graphic_ids.add(graphic_id) - - # TODO: Terrains + create_graphics_requests = staticmethod(create_graphics_requests) diff --git a/openage/convert/processor/conversion/de1/processor.py b/openage/convert/processor/conversion/de1/processor.py index cb3f54cc91..d68f732cf5 100644 --- a/openage/convert/processor/conversion/de1/processor.py +++ b/openage/convert/processor/conversion/de1/processor.py @@ -8,7 +8,6 @@ from .....log import info -from ....entity_object.conversion.aoc.genie_graphic import GenieGraphic from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer from ....service.debug_info import debug_converter_objects, \ debug_converter_object_groups @@ -19,13 +18,14 @@ from ..ror.processor import RoRProcessor from .media_subprocessor import DE1MediaSubprocessor from .modpack_subprocessor import DE1ModpackSubprocessor +from .main.extract.graphics import extract_genie_graphics if typing.TYPE_CHECKING: from argparse import Namespace - from openage.convert.entity_object.conversion.stringresource import StringResource - from openage.convert.entity_object.conversion.modpack import Modpack - from openage.convert.value_object.read.value_members import ArrayMember - from openage.convert.value_object.init.game_version import GameVersion + from ....entity_object.conversion.stringresource import StringResource + from ....entity_object.conversion.modpack import Modpack + from ....value_object.read.value_members import ArrayMember + from ....value_object.init.game_version import GameVersion class DE1Processor: @@ -47,9 +47,7 @@ def convert( :param gamespec: Gamedata from empires.dat read in by the reader functions. - :type gamespec: class: ...dataformat.value_members.ArrayMember :returns: A list of modpacks. - :rtype: list """ info("Starting conversion...") @@ -84,11 +82,9 @@ def _pre_processor( Store data from the reader in a conversion container. :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember :param full_data_set: GenieObjectContainer instance that contains all relevant data for the conversion process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer """ dataset = GenieObjectContainer() @@ -104,13 +100,15 @@ def _pre_processor( AoCProcessor.extract_genie_effect_bundles(gamespec, dataset) AoCProcessor.sanitize_effect_bundles(dataset) AoCProcessor.extract_genie_civs(gamespec, dataset) - cls.extract_genie_graphics(gamespec, dataset) + extract_genie_graphics(gamespec, dataset) RoRProcessor.extract_genie_sounds(gamespec, dataset) AoCProcessor.extract_genie_terrains(gamespec, dataset) AoCProcessor.extract_genie_restrictions(gamespec, dataset) return dataset + extract_genie_graphics = staticmethod(extract_genie_graphics) + @classmethod def _processor( cls, @@ -122,11 +120,9 @@ def _processor( Python objects. :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember :param full_data_set: GenieObjectContainer instance that contains all relevant data for the conversion process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer """ info("Creating API-like objects...") @@ -161,7 +157,6 @@ def _post_processor(cls, full_data_set: GenieObjectContainer) -> list[Modpack]: :param full_data_set: GenieObjectContainer instance that contains all relevant data for the conversion process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer """ info("Creating nyan objects...") @@ -172,41 +167,3 @@ def _post_processor(cls, full_data_set: GenieObjectContainer) -> list[Modpack]: DE1MediaSubprocessor.convert(full_data_set) return DE1ModpackSubprocessor.get_modpacks(full_data_set) - - @staticmethod - def extract_genie_graphics(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract graphic definitions from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->graphics - raw_graphics = gamespec[0]["graphics"].value - - for raw_graphic in raw_graphics: - # Can be ignored if there is no filename associated - filename = raw_graphic["filename"].value - if not filename: - continue - - # DE1 stores most graphics filenames as 'whatever_' - # where '' must be replaced by x1, x2 or x4 - # which corresponds to the graphics resolution variant - # - # we look for the x1 variant - if filename.endswith(""): - filename = f"{filename[:-4]}x1" - - graphic_id = raw_graphic["graphic_id"].value - graphic_members = raw_graphic.value - - graphic = GenieGraphic(graphic_id, full_data_set, members=graphic_members) - if filename.lower() not in full_data_set.existing_graphics: - graphic.exists = False - - full_data_set.genie_graphics.update({graphic.get_id(): graphic}) - - # Detect subgraphics - for genie_graphic in full_data_set.genie_graphics.values(): - genie_graphic.detect_subgraphics() From bfdc957b4374d27d594da26187985ec9b4183af0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 15:33:18 +0200 Subject: [PATCH 125/163] convert: Move DE2AbilitySubprocessor methods into separate files. --- .../processor/conversion/de2/CMakeLists.txt | 2 + .../conversion/de2/ability/CMakeLists.txt | 4 + .../conversion/de2/ability/__init__.py | 5 + .../de2/ability/regenerate_attribute.py | 94 +++++++++++++++++++ .../conversion/de2/ability_subprocessor.py | 94 +------------------ 5 files changed, 107 insertions(+), 92 deletions(-) create mode 100644 openage/convert/processor/conversion/de2/ability/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/ability/__init__.py create mode 100644 openage/convert/processor/conversion/de2/ability/regenerate_attribute.py diff --git a/openage/convert/processor/conversion/de2/CMakeLists.txt b/openage/convert/processor/conversion/de2/CMakeLists.txt index cbed4dc792..6b69427ba9 100644 --- a/openage/convert/processor/conversion/de2/CMakeLists.txt +++ b/openage/convert/processor/conversion/de2/CMakeLists.txt @@ -10,3 +10,5 @@ add_py_modules( upgrade_attribute_subprocessor.py upgrade_resource_subprocessor.py ) + +add_subdirectory(ability) diff --git a/openage/convert/processor/conversion/de2/ability/CMakeLists.txt b/openage/convert/processor/conversion/de2/ability/CMakeLists.txt new file mode 100644 index 0000000000..30cf8ec3e4 --- /dev/null +++ b/openage/convert/processor/conversion/de2/ability/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + regenerate_attribute.py +) diff --git a/openage/convert/processor/conversion/de2/ability/__init__.py b/openage/convert/processor/conversion/de2/ability/__init__.py new file mode 100644 index 0000000000..614701233a --- /dev/null +++ b/openage/convert/processor/conversion/de2/ability/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Derives and adds abilities to game entities created from lines. +""" diff --git a/openage/convert/processor/conversion/de2/ability/regenerate_attribute.py b/openage/convert/processor/conversion/de2/ability/regenerate_attribute.py new file mode 100644 index 0000000000..2815e9bd7c --- /dev/null +++ b/openage/convert/processor/conversion/de2/ability/regenerate_attribute.py @@ -0,0 +1,94 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the RegenerateAttribute ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def regenerate_attribute_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the RegenerateAttribute ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward references for the ability. + """ + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + + attribute = None + attribute_name = "" + if current_unit_id == 125: + # Monk; regenerates Faith + attribute = dataset.pregen_nyan_objects["util.attribute.types.Faith"].get_nyan_object() + attribute_name = "Faith" + + elif current_unit_id == 692: + # Berserk: regenerates Health + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + attribute_name = "Health" + + else: + return [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_name = f"Regenerate{attribute_name}" + 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("engine.ability.type.RegenerateAttribute") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Attribute rate + # =============================================================================== + rate_name = f"{attribute_name}Rate" + rate_ref = f"{game_entity_name}.{ability_name}.{rate_name}" + rate_raw_api_object = RawAPIObject(rate_ref, rate_name, dataset.nyan_api_objects) + rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") + rate_location = ForwardRef(line, ability_ref) + rate_raw_api_object.set_location(rate_location) + + # Attribute + rate_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeRate") + + # Rate + attribute_rate = 0 + if current_unit_id == 125: + # stored in civ resources + attribute_rate = dataset.genie_civs[0]["resources"][35].value + + elif current_unit_id == 692: + # stored in unit, but has to get converted to amount/second + heal_timer = current_unit["heal_timer"].value + attribute_rate = 1 / heal_timer + + rate_raw_api_object.add_raw_member("rate", + attribute_rate, + "engine.util.attribute.AttributeRate") + + line.add_raw_api_object(rate_raw_api_object) + # =============================================================================== + rate_forward_ref = ForwardRef(line, rate_ref) + ability_raw_api_object.add_raw_member("rate", + rate_forward_ref, + "engine.ability.type.RegenerateAttribute") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return [ability_forward_ref] diff --git a/openage/convert/processor/conversion/de2/ability_subprocessor.py b/openage/convert/processor/conversion/de2/ability_subprocessor.py index 8ee78e01e6..6a58511574 100644 --- a/openage/convert/processor/conversion/de2/ability_subprocessor.py +++ b/openage/convert/processor/conversion/de2/ability_subprocessor.py @@ -1,21 +1,11 @@ # Copyright 2021-2022 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-few-public-methods,too-many-locals """ Derives and adds abilities to lines. Subroutine of the nyan subprocessor. """ -from __future__ import annotations -import typing - -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup +from .ability.regenerate_attribute import regenerate_attribute_ability class DE2AbilitySubprocessor: @@ -23,84 +13,4 @@ class DE2AbilitySubprocessor: Creates raw API objects for abilities in DE2. """ - @staticmethod - def regenerate_attribute_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the RegenerateAttribute ability to a line. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward references for the ability. - :rtype: list - """ - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - - attribute = None - attribute_name = "" - if current_unit_id == 125: - # Monk; regenerates Faith - attribute = dataset.pregen_nyan_objects["util.attribute.types.Faith"].get_nyan_object() - attribute_name = "Faith" - - elif current_unit_id == 692: - # Berserk: regenerates Health - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - attribute_name = "Health" - - else: - return [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_name = f"Regenerate{attribute_name}" - 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("engine.ability.type.RegenerateAttribute") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Attribute rate - # =============================================================================== - rate_name = f"{attribute_name}Rate" - rate_ref = f"{game_entity_name}.{ability_name}.{rate_name}" - rate_raw_api_object = RawAPIObject(rate_ref, rate_name, dataset.nyan_api_objects) - rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") - rate_location = ForwardRef(line, ability_ref) - rate_raw_api_object.set_location(rate_location) - - # Attribute - rate_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeRate") - - # Rate - attribute_rate = 0 - if current_unit_id == 125: - # stored in civ resources - attribute_rate = dataset.genie_civs[0]["resources"][35].value - - elif current_unit_id == 692: - # stored in unit, but has to get converted to amount/second - heal_timer = current_unit["heal_timer"].value - attribute_rate = 1 / heal_timer - - rate_raw_api_object.add_raw_member("rate", - attribute_rate, - "engine.util.attribute.AttributeRate") - - line.add_raw_api_object(rate_raw_api_object) - # =============================================================================== - rate_forward_ref = ForwardRef(line, rate_ref) - ability_raw_api_object.add_raw_member("rate", - rate_forward_ref, - "engine.ability.type.RegenerateAttribute") - - line.add_raw_api_object(ability_raw_api_object) - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return [ability_forward_ref] + regenerate_attribute_ability = staticmethod(regenerate_attribute_ability) From 6c132494987b996077847c8ecc9e5051caf99f7a Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 15:38:09 +0200 Subject: [PATCH 126/163] convert: Refactor DE2CivSubprocessor into separate files. --- .../processor/conversion/de2/CMakeLists.txt | 1 + .../conversion/de2/civ/CMakeLists.txt | 4 + .../processor/conversion/de2/civ/__init__.py | 5 + .../processor/conversion/de2/civ/civ_bonus.py | 128 ++++++++++++++++++ .../conversion/de2/civ_subprocessor.py | 124 +---------------- 5 files changed, 143 insertions(+), 119 deletions(-) create mode 100644 openage/convert/processor/conversion/de2/civ/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/civ/__init__.py create mode 100644 openage/convert/processor/conversion/de2/civ/civ_bonus.py diff --git a/openage/convert/processor/conversion/de2/CMakeLists.txt b/openage/convert/processor/conversion/de2/CMakeLists.txt index 6b69427ba9..6fe8875927 100644 --- a/openage/convert/processor/conversion/de2/CMakeLists.txt +++ b/openage/convert/processor/conversion/de2/CMakeLists.txt @@ -12,3 +12,4 @@ add_py_modules( ) add_subdirectory(ability) +add_subdirectory(civ) diff --git a/openage/convert/processor/conversion/de2/civ/CMakeLists.txt b/openage/convert/processor/conversion/de2/civ/CMakeLists.txt new file mode 100644 index 0000000000..63189563b5 --- /dev/null +++ b/openage/convert/processor/conversion/de2/civ/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + civ_bonus.py +) diff --git a/openage/convert/processor/conversion/de2/civ/__init__.py b/openage/convert/processor/conversion/de2/civ/__init__.py new file mode 100644 index 0000000000..4c766c2781 --- /dev/null +++ b/openage/convert/processor/conversion/de2/civ/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches and modifiers for civs. +""" diff --git a/openage/convert/processor/conversion/de2/civ/civ_bonus.py b/openage/convert/processor/conversion/de2/civ/civ_bonus.py new file mode 100644 index 0000000000..0b7f12c4e0 --- /dev/null +++ b/openage/convert/processor/conversion/de2/civ/civ_bonus.py @@ -0,0 +1,128 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ bonuses. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..tech_subprocessor import DE2TechSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def setup_civ_bonus(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Returns global modifiers of a civ. + """ + patches = [] + + civ_id = civ_group.get_id() + dataset = civ_group.data + + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + civ_name = civ_lookup_dict[civ_id][0] + + # key: tech_id; value patched in patches + tech_patches = {} + + for civ_bonus in civ_group.civ_boni.values(): + if not civ_bonus.replaces_researchable_tech(): + bonus_patches = DE2TechSubprocessor.get_patches(civ_bonus) + + # civ boni might be unlocked by age ups. if so, patch them into the age up + # patches are queued here + required_tech_count = civ_bonus.tech["required_tech_count"].value + if required_tech_count > 0 and len(bonus_patches) > 0: + if required_tech_count == 1: + tech_id = civ_bonus.tech["required_techs"][0].value + + elif required_tech_count == 2: + # Try to patch them into the second listed tech + # This tech is usually unlocked by an age up + tech_id = civ_bonus.tech["required_techs"][1].value + + if tech_id == 232: + # Synergies with other civs (usually chinese farming bonus) + # TODO: This must be solved in a better way than in the Genie dataset. + continue + + if tech_id not in dataset.tech_groups.keys(): + # Circumvents a "funny" duplicate castle age up tech for Incas + # The required tech of the duplicate is the age up we are looking for + tech_id = dataset.genie_techs[tech_id]["required_techs"][0].value + + if not dataset.tech_groups[tech_id].is_researchable(): + # Fall back to the first tech if the second is not researchable + tech_id = civ_bonus.tech["required_techs"][0].value + + if tech_id == 104: + # Skip Dark Age; it is not a tech in openage + patches.extend(bonus_patches) + + if tech_id not in dataset.tech_groups.keys() or\ + not dataset.tech_groups[tech_id].is_researchable(): + # TODO: Bonus unlocked by something else + continue + + if tech_id in tech_patches: + tech_patches[tech_id].extend(bonus_patches) + + else: + tech_patches[tech_id] = bonus_patches + + else: + patches.extend(bonus_patches) + + for tech_id, patches in tech_patches.items(): + tech_group = dataset.tech_groups[tech_id] + tech_name = tech_lookup_dict[tech_id][0] + + patch_target_ref = f"{tech_name}" + patch_target_forward_ref = ForwardRef(tech_group, patch_target_ref) + + # Wrapper + wrapper_name = f"{tech_name}CivBonusWrapper" + wrapper_ref = f"{civ_name}.{wrapper_name}" + wrapper_location = ForwardRef(civ_group, civ_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"{tech_name}CivBonus" + nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" + nyan_patch_location = ForwardRef(civ_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) + + nyan_patch_raw_api_object.add_raw_patch_member("updates", + patches, + "engine.util.tech.Tech", + MemberOperator.ADD) + + patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + civ_group.add_raw_api_object(wrapper_raw_api_object) + civ_group.add_raw_api_object(nyan_patch_raw_api_object) + + wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) + patches.append(wrapper_forward_ref) + + return patches diff --git a/openage/convert/processor/conversion/de2/civ_subprocessor.py b/openage/convert/processor/conversion/de2/civ_subprocessor.py index 32e6e00fb9..25379ff7cf 100644 --- a/openage/convert/processor/conversion/de2/civ_subprocessor.py +++ b/openage/convert/processor/conversion/de2/civ_subprocessor.py @@ -1,6 +1,4 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-statements,too-many-branches +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates patches and modifiers for civs. @@ -8,15 +6,13 @@ from __future__ import annotations import typing -from .....nyan.nyan_structs import MemberOperator -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups from ....value_object.conversion.forward_ref import ForwardRef from ..aoc.civ_subprocessor import AoCCivSubprocessor +from .civ.civ_bonus import setup_civ_bonus from .tech_subprocessor import DE2TechSubprocessor if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + from ....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup class DE2CivSubprocessor: @@ -35,121 +31,11 @@ def get_civ_setup(cls, civ_group: GenieCivilizationGroup) -> list[ForwardRef]: patches.extend(AoCCivSubprocessor.setup_unique_units(civ_group)) patches.extend(AoCCivSubprocessor.setup_unique_techs(civ_group)) patches.extend(AoCCivSubprocessor.setup_tech_tree(civ_group)) - patches.extend(cls.setup_civ_bonus(civ_group)) + patches.extend(setup_civ_bonus(civ_group)) if len(civ_group.get_team_bonus_effects()) > 0: patches.extend(DE2TechSubprocessor.get_patches(civ_group.team_bonus)) return patches - @classmethod - def setup_civ_bonus(cls, civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Returns global modifiers of a civ. - """ - patches = [] - - civ_id = civ_group.get_id() - dataset = civ_group.data - - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - civ_name = civ_lookup_dict[civ_id][0] - - # key: tech_id; value patched in patches - tech_patches = {} - - for civ_bonus in civ_group.civ_boni.values(): - if not civ_bonus.replaces_researchable_tech(): - bonus_patches = DE2TechSubprocessor.get_patches(civ_bonus) - - # civ boni might be unlocked by age ups. if so, patch them into the age up - # patches are queued here - required_tech_count = civ_bonus.tech["required_tech_count"].value - if required_tech_count > 0 and len(bonus_patches) > 0: - if required_tech_count == 1: - tech_id = civ_bonus.tech["required_techs"][0].value - - elif required_tech_count == 2: - # Try to patch them into the second listed tech - # This tech is usually unlocked by an age up - tech_id = civ_bonus.tech["required_techs"][1].value - - if tech_id == 232: - # Synergies with other civs (usually chinese farming bonus) - # TODO: This must be solved in a better way than in the Genie dataset. - continue - - if tech_id not in dataset.tech_groups.keys(): - # Circumvents a "funny" duplicate castle age up tech for Incas - # The required tech of the duplicate is the age up we are looking for - tech_id = dataset.genie_techs[tech_id]["required_techs"][0].value - - if not dataset.tech_groups[tech_id].is_researchable(): - # Fall back to the first tech if the second is not researchable - tech_id = civ_bonus.tech["required_techs"][0].value - - if tech_id == 104: - # Skip Dark Age; it is not a tech in openage - patches.extend(bonus_patches) - - if tech_id not in dataset.tech_groups.keys() or\ - not dataset.tech_groups[tech_id].is_researchable(): - # TODO: Bonus unlocked by something else - continue - - if tech_id in tech_patches: - tech_patches[tech_id].extend(bonus_patches) - - else: - tech_patches[tech_id] = bonus_patches - - else: - patches.extend(bonus_patches) - - for tech_id, patches in tech_patches.items(): - tech_group = dataset.tech_groups[tech_id] - tech_name = tech_lookup_dict[tech_id][0] - - patch_target_ref = f"{tech_name}" - patch_target_forward_ref = ForwardRef(tech_group, patch_target_ref) - - # Wrapper - wrapper_name = f"{tech_name}CivBonusWrapper" - wrapper_ref = f"{civ_name}.{wrapper_name}" - wrapper_location = ForwardRef(civ_group, civ_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"{tech_name}CivBonus" - nyan_patch_ref = f"{civ_name}.{wrapper_name}.{nyan_patch_name}" - nyan_patch_location = ForwardRef(civ_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) - - nyan_patch_raw_api_object.add_raw_patch_member("updates", - patches, - "engine.util.tech.Tech", - MemberOperator.ADD) - - patch_forward_ref = ForwardRef(civ_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - civ_group.add_raw_api_object(wrapper_raw_api_object) - civ_group.add_raw_api_object(nyan_patch_raw_api_object) - - wrapper_forward_ref = ForwardRef(civ_group, wrapper_ref) - patches.append(wrapper_forward_ref) - - return patches + setup_civ_bonus = staticmethod(setup_civ_bonus) From d40a61078dc7e71bc670bb120be3a3603268382d Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 15:44:23 +0200 Subject: [PATCH 127/163] convert: Refactor DE2MediaSubprocessor into separate files. --- .../processor/conversion/de2/CMakeLists.txt | 1 + .../conversion/de2/media/CMakeLists.txt | 5 + .../conversion/de2/media/__init__.py | 5 + .../conversion/de2/media/graphics.py | 101 +++++++++++++++++ .../processor/conversion/de2/media/sound.py | 17 +++ .../conversion/de2/media_subprocessor.py | 107 ++---------------- 6 files changed, 136 insertions(+), 100 deletions(-) create mode 100644 openage/convert/processor/conversion/de2/media/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/media/__init__.py create mode 100644 openage/convert/processor/conversion/de2/media/graphics.py create mode 100644 openage/convert/processor/conversion/de2/media/sound.py diff --git a/openage/convert/processor/conversion/de2/CMakeLists.txt b/openage/convert/processor/conversion/de2/CMakeLists.txt index 6fe8875927..e26e3f06d9 100644 --- a/openage/convert/processor/conversion/de2/CMakeLists.txt +++ b/openage/convert/processor/conversion/de2/CMakeLists.txt @@ -13,3 +13,4 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(civ) +add_subdirectory(media) diff --git a/openage/convert/processor/conversion/de2/media/CMakeLists.txt b/openage/convert/processor/conversion/de2/media/CMakeLists.txt new file mode 100644 index 0000000000..74a1ead506 --- /dev/null +++ b/openage/convert/processor/conversion/de2/media/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py + graphics.py + sound.py +) diff --git a/openage/convert/processor/conversion/de2/media/__init__.py b/openage/convert/processor/conversion/de2/media/__init__.py new file mode 100644 index 0000000000..45c2736e8b --- /dev/null +++ b/openage/convert/processor/conversion/de2/media/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create media export requests for media files in the DE2 data. +""" diff --git a/openage/convert/processor/conversion/de2/media/graphics.py b/openage/convert/processor/conversion/de2/media/graphics.py new file mode 100644 index 0000000000..ea40b6ad61 --- /dev/null +++ b/openage/convert/processor/conversion/de2/media/graphics.py @@ -0,0 +1,101 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create export requests for graphics files. +""" +from __future__ import annotations +import typing + +from .....entity_object.export.formats.sprite_metadata import LayerMode +from .....entity_object.export.media_export_request import MediaExportRequest +from .....entity_object.export.metadata_export import SpriteMetadataExport +from .....entity_object.export.metadata_export import TextureMetadataExport +from .....value_object.read.media_types import MediaType + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: + """ + Create export requests for graphics referenced by CombinedSprite objects. + """ + combined_sprites = full_data_set.combined_sprites.values() + handled_graphic_ids = set() + + for sprite in combined_sprites: + ref_graphics = sprite.get_graphics() + graphic_targetdirs = sprite.resolve_graphics_location() + + # Animation metadata file definiton + sprite_meta_filename = f"{sprite.get_filename()}.sprite" + sprite_meta_export = SpriteMetadataExport(sprite.resolve_sprite_location(), + sprite_meta_filename) + full_data_set.metadata_exports.append(sprite_meta_export) + + for graphic in ref_graphics: + graphic_id = graphic.get_id() + if graphic_id in handled_graphic_ids: + continue + + targetdir = graphic_targetdirs[graphic_id] + source_filename = f"{str(graphic['filename'].value)}.sld" + target_filename = f"{sprite.get_filename()}_{str(graphic['slp_id'].value)}.png" + + export_request = MediaExportRequest(MediaType.GRAPHICS, + targetdir, + source_filename, + target_filename) + full_data_set.graphics_exports.update({graphic_id: export_request}) + + # Texture metadata file definiton + # Same file stem as the image file and same targetdir + texture_meta_filename = f"{target_filename[:-4]}.texture" + texture_meta_export = TextureMetadataExport(targetdir, + texture_meta_filename) + full_data_set.metadata_exports.append(texture_meta_export) + + # Add texture image filename to texture metadata + texture_meta_export.add_imagefile(target_filename) + + # Add metadata from graphics to animation metadata + sequence_type = graphic["sequence_type"].value + if sequence_type == 0x00: + layer_mode = LayerMode.OFF + + elif sequence_type & 0x08: + layer_mode = LayerMode.ONCE + + else: + layer_mode = LayerMode.LOOP + + layer_pos = graphic["layer"].value + frame_rate = round(graphic["frame_rate"].value, ndigits=6) + if frame_rate < 0.000001: + frame_rate = None + + replay_delay = round(graphic["replay_delay"].value, ndigits=6) + if replay_delay < 0.000001: + replay_delay = None + + frame_count = graphic["frame_count"].value + angle_count = graphic["angle_count"].value + # mirror_mode = graphic["mirroring_mode"].value + sprite_meta_export.add_graphics_metadata(target_filename, + texture_meta_filename, + layer_mode, + layer_pos, + frame_rate, + replay_delay, + frame_count, + angle_count, + mirror_mode=0, + start_angle=270) + + # Notify metadata export about SMX metadata when the file is exported + export_request.add_observer(texture_meta_export) + export_request.add_observer(sprite_meta_export) + + handled_graphic_ids.add(graphic_id) + + # TODO: Terrain exports (DDS files) diff --git a/openage/convert/processor/conversion/de2/media/sound.py b/openage/convert/processor/conversion/de2/media/sound.py new file mode 100644 index 0000000000..378b36c551 --- /dev/null +++ b/openage/convert/processor/conversion/de2/media/sound.py @@ -0,0 +1,17 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create export requests for sound files. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_sound_requests(full_data_set: GenieObjectContainer) -> None: + """ + Create export requests for sounds referenced by CombinedSound objects. + """ + # TODO: Sound exports (Wwise files) diff --git a/openage/convert/processor/conversion/de2/media_subprocessor.py b/openage/convert/processor/conversion/de2/media_subprocessor.py index df16babc35..d1f68ec76d 100644 --- a/openage/convert/processor/conversion/de2/media_subprocessor.py +++ b/openage/convert/processor/conversion/de2/media_subprocessor.py @@ -8,15 +8,11 @@ from __future__ import annotations import typing -from ....entity_object.export.formats.sprite_metadata import LayerMode -from ....entity_object.export.media_export_request import MediaExportRequest -from ....entity_object.export.metadata_export import SpriteMetadataExport -from ....entity_object.export.metadata_export import TextureMetadataExport -from ....value_object.read.media_types import MediaType +from .media.graphics import create_graphics_requests +from .media.sound import create_sound_requests if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_object_container\ - import GenieObjectContainer + from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer class DE2MediaSubprocessor: @@ -29,97 +25,8 @@ def convert(cls, full_data_set: GenieObjectContainer) -> None: """ Create all export requests for the dataset. """ - cls.create_graphics_requests(full_data_set) - cls.create_sound_requests(full_data_set) + create_graphics_requests(full_data_set) + create_sound_requests(full_data_set) - @staticmethod - def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: - """ - Create export requests for graphics referenced by CombinedSprite objects. - """ - combined_sprites = full_data_set.combined_sprites.values() - handled_graphic_ids = set() - - for sprite in combined_sprites: - ref_graphics = sprite.get_graphics() - graphic_targetdirs = sprite.resolve_graphics_location() - - # Animation metadata file definiton - sprite_meta_filename = f"{sprite.get_filename()}.sprite" - sprite_meta_export = SpriteMetadataExport(sprite.resolve_sprite_location(), - sprite_meta_filename) - full_data_set.metadata_exports.append(sprite_meta_export) - - for graphic in ref_graphics: - graphic_id = graphic.get_id() - if graphic_id in handled_graphic_ids: - continue - - targetdir = graphic_targetdirs[graphic_id] - source_filename = f"{str(graphic['filename'].value)}.sld" - target_filename = f"{sprite.get_filename()}_{str(graphic['slp_id'].value)}.png" - - export_request = MediaExportRequest(MediaType.GRAPHICS, - targetdir, - source_filename, - target_filename) - full_data_set.graphics_exports.update({graphic_id: export_request}) - - # Texture metadata file definiton - # Same file stem as the image file and same targetdir - texture_meta_filename = f"{target_filename[:-4]}.texture" - texture_meta_export = TextureMetadataExport(targetdir, - texture_meta_filename) - full_data_set.metadata_exports.append(texture_meta_export) - - # Add texture image filename to texture metadata - texture_meta_export.add_imagefile(target_filename) - - # Add metadata from graphics to animation metadata - sequence_type = graphic["sequence_type"].value - if sequence_type == 0x00: - layer_mode = LayerMode.OFF - - elif sequence_type & 0x08: - layer_mode = LayerMode.ONCE - - else: - layer_mode = LayerMode.LOOP - - layer_pos = graphic["layer"].value - frame_rate = round(graphic["frame_rate"].value, ndigits=6) - if frame_rate < 0.000001: - frame_rate = None - - replay_delay = round(graphic["replay_delay"].value, ndigits=6) - if replay_delay < 0.000001: - replay_delay = None - - frame_count = graphic["frame_count"].value - angle_count = graphic["angle_count"].value - # mirror_mode = graphic["mirroring_mode"].value - sprite_meta_export.add_graphics_metadata(target_filename, - texture_meta_filename, - layer_mode, - layer_pos, - frame_rate, - replay_delay, - frame_count, - angle_count, - mirror_mode=0, - start_angle=270) - - # Notify metadata export about SMX metadata when the file is exported - export_request.add_observer(texture_meta_export) - export_request.add_observer(sprite_meta_export) - - handled_graphic_ids.add(graphic_id) - - # TODO: Terrain exports (DDS files) - - @staticmethod - def create_sound_requests(full_data_set: GenieObjectContainer) -> None: - """ - Create export requests for sounds referenced by CombinedSound objects. - """ - # TODO: Sound exports (Wwise files) + create_graphics_requests = staticmethod(create_graphics_requests) + create_sound_requests = staticmethod(create_sound_requests) From 153c3f2b93ff84fc8ba5b31ea9299d6a51e6cf28 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 15:59:26 +0200 Subject: [PATCH 128/163] convert: Refactor DE2NyanSubprocessor into separate files. --- .../processor/conversion/de2/CMakeLists.txt | 1 + .../conversion/de2/nyan/CMakeLists.txt | 8 + .../processor/conversion/de2/nyan/__init__.py | 6 + .../processor/conversion/de2/nyan/building.py | 181 ++++ .../processor/conversion/de2/nyan/civ.py | 140 +++ .../processor/conversion/de2/nyan/tech.py | 139 +++ .../processor/conversion/de2/nyan/terrain.py | 213 +++++ .../processor/conversion/de2/nyan/unit.py | 237 +++++ .../conversion/de2/nyan_subprocessor.py | 843 +----------------- 9 files changed, 939 insertions(+), 829 deletions(-) create mode 100644 openage/convert/processor/conversion/de2/nyan/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/nyan/__init__.py create mode 100644 openage/convert/processor/conversion/de2/nyan/building.py create mode 100644 openage/convert/processor/conversion/de2/nyan/civ.py create mode 100644 openage/convert/processor/conversion/de2/nyan/tech.py create mode 100644 openage/convert/processor/conversion/de2/nyan/terrain.py create mode 100644 openage/convert/processor/conversion/de2/nyan/unit.py diff --git a/openage/convert/processor/conversion/de2/CMakeLists.txt b/openage/convert/processor/conversion/de2/CMakeLists.txt index e26e3f06d9..04691ba629 100644 --- a/openage/convert/processor/conversion/de2/CMakeLists.txt +++ b/openage/convert/processor/conversion/de2/CMakeLists.txt @@ -14,3 +14,4 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(civ) add_subdirectory(media) +add_subdirectory(nyan) diff --git a/openage/convert/processor/conversion/de2/nyan/CMakeLists.txt b/openage/convert/processor/conversion/de2/nyan/CMakeLists.txt new file mode 100644 index 0000000000..8f4390991f --- /dev/null +++ b/openage/convert/processor/conversion/de2/nyan/CMakeLists.txt @@ -0,0 +1,8 @@ +add_py_modules( + __init__.py + building.py + civ.py + tech.py + terrain.py + unit.py +) diff --git a/openage/convert/processor/conversion/de2/nyan/__init__.py b/openage/convert/processor/conversion/de2/nyan/__init__.py new file mode 100644 index 0000000000..3d3157d6a2 --- /dev/null +++ b/openage/convert/processor/conversion/de2/nyan/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert API-like objects to nyan objects. Subroutine of the +main DE2 processor. +""" diff --git a/openage/convert/processor/conversion/de2/nyan/building.py b/openage/convert/processor/conversion/de2/nyan/building.py new file mode 100644 index 0000000000..f015f428f9 --- /dev/null +++ b/openage/convert/processor/conversion/de2/nyan/building.py @@ -0,0 +1,181 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert building lines to openage game entities. +""" + +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ...aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor +from ...aoc.nyan_subprocessor import AoCNyanSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup + + +def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: + """ + Creates raw API objects for a building line. + + :param building_line: Building line that gets converted to a game entity. + """ + current_building = building_line.line[0] + current_building_id = building_line.get_head_unit_id() + dataset = building_line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[current_building_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[current_building_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[current_building_id][1]) + building_line.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give a building two types + # - util.game_entity_type.types.Building (if unit_type >= 80) + # - util.game_entity_type.types. (depending on the class) + # and additionally + # - util.game_entity_type.types.DropSite (only if this is used as a drop site) + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + unit_type = current_building["unit_type"].value + + if unit_type >= 80: + type_obj = dataset.pregen_nyan_objects[ + "util.game_entity_type.types.Building" + ].get_nyan_object() + types_set.append(type_obj) + + unit_class = current_building["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + if building_line.is_dropsite(): + type_obj = dataset.pregen_nyan_objects[ + "util.game_entity_type.types.DropSite" + ].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + abilities_set.append(AoCAbilitySubprocessor.attribute_change_tracker_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.death_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.resistance_ability(building_line)) + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.stop_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(building_line)) + + if building_line.get_head_unit()["speed"].value > 0: + abilities_set.append(AoCAbilitySubprocessor.move_ability(building_line)) + + # Config abilities + if building_line.is_creatable(): + abilities_set.append(AoCAbilitySubprocessor.constructable_ability(building_line)) + + if not building_line.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(building_line)) + + if building_line.has_foundation(): + if building_line.get_class_id() == 49: + # Use OverlayTerrain for the farm terrain + abilities_set.append(AoCAbilitySubprocessor.overlay_terrain_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line, + terrain_id=27)) + + else: + abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line)) + + # Creation/Research abilities + if len(building_line.creates) > 0: + abilities_set.append(AoCAbilitySubprocessor.create_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.production_queue_ability(building_line)) + + if len(building_line.researches) > 0: + abilities_set.append(AoCAbilitySubprocessor.research_ability(building_line)) + + # Effect abilities + if building_line.is_projectile_shooter(): + abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(building_line, 7)) + abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(building_line)) + AoCNyanSubprocessor.projectiles_from_line(building_line) + + # Storage abilities + if building_line.is_garrison(): + abilities_set.append(AoCAbilitySubprocessor.storage_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(building_line)) + + garrison_mode = building_line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.NATURAL: + abilities_set.append( + AoCAbilitySubprocessor.send_back_to_task_ability(building_line)) + + if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): + abilities_set.append(AoCAbilitySubprocessor.rally_point_ability(building_line)) + + # Resource abilities + if building_line.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(building_line)) + + if building_line.is_dropsite(): + abilities_set.append(AoCAbilitySubprocessor.drop_site_ability(building_line)) + + ability = AoCAbilitySubprocessor.provide_contingent_ability(building_line) + if ability: + abilities_set.append(ability) + + # Trade abilities + if building_line.is_trade_post(): + abilities_set.append(AoCAbilitySubprocessor.trade_post_ability(building_line)) + + if building_line.get_id() == 84: + # Market trading + abilities_set.extend(AoCAbilitySubprocessor.exchange_resources_ability(building_line)) + + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + raw_api_object.add_raw_member("modifiers", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Misc (Objects that are not used by the unit line itself, but use its values) + # ======================================================================= + if building_line.is_creatable(): + AoCAuxiliarySubprocessor.get_creatable_game_entity(building_line) diff --git a/openage/convert/processor/conversion/de2/nyan/civ.py b/openage/convert/processor/conversion/de2/nyan/civ.py new file mode 100644 index 0000000000..90aea0b877 --- /dev/null +++ b/openage/convert/processor/conversion/de2/nyan/civ.py @@ -0,0 +1,140 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert civ groups to openage player setups. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.civ_subprocessor import AoCCivSubprocessor +from ..civ_subprocessor import DE2CivSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +@staticmethod +def civ_group_to_civ(civ_group: GenieCivilizationGroup) -> None: + """ + Creates raw API objects for a civ group. + + :param civ_group: Terrain group that gets converted to a tech. + """ + civ_id = civ_group.get_id() + + dataset = civ_group.data + + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + # Start with the Tech object + tech_name = civ_lookup_dict[civ_id][0] + raw_api_object = RawAPIObject(tech_name, tech_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.setup.PlayerSetup") + + obj_location = f"data/civ/{civ_lookup_dict[civ_id][1]}/" + + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(civ_lookup_dict[civ_id][1]) + civ_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{tech_name}.{tech_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{tech_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(civ_group, tech_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(civ_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Description + # ======================================================================= + description_ref = f"{tech_name}.{tech_name}Description" + description_raw_api_object = RawAPIObject(description_ref, + f"{tech_name}Description", + dataset.nyan_api_objects) + description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + description_location = ForwardRef(civ_group, tech_name) + description_raw_api_object.set_location(description_location) + + description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + description_forward_ref = ForwardRef(civ_group, description_ref) + raw_api_object.add_raw_member("description", + description_forward_ref, + "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(description_raw_api_object) + + # ======================================================================= + # Long description + # ======================================================================= + long_description_ref = f"{tech_name}.{tech_name}LongDescription" + long_description_raw_api_object = RawAPIObject(long_description_ref, + f"{tech_name}LongDescription", + dataset.nyan_api_objects) + long_description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + long_description_location = ForwardRef(civ_group, tech_name) + long_description_raw_api_object.set_location(long_description_location) + + long_description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + long_description_forward_ref = ForwardRef(civ_group, long_description_ref) + raw_api_object.add_raw_member("long_description", + long_description_forward_ref, + "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(long_description_raw_api_object) + + # ======================================================================= + # TODO: Leader names + # ======================================================================= + raw_api_object.add_raw_member("leader_names", + [], + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers = AoCCivSubprocessor.get_modifiers(civ_group) + raw_api_object.add_raw_member("modifiers", + modifiers, + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Starting resources + # ======================================================================= + resource_amounts = AoCCivSubprocessor.get_starting_resources(civ_group) + raw_api_object.add_raw_member("starting_resources", + resource_amounts, + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Game setup + # ======================================================================= + game_setup = DE2CivSubprocessor.get_civ_setup(civ_group) + raw_api_object.add_raw_member("game_setup", + game_setup, + "engine.util.setup.PlayerSetup") diff --git a/openage/convert/processor/conversion/de2/nyan/tech.py b/openage/convert/processor/conversion/de2/nyan/tech.py new file mode 100644 index 0000000000..5d2e26e82a --- /dev/null +++ b/openage/convert/processor/conversion/de2/nyan/tech.py @@ -0,0 +1,139 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert tech groups to openage techs. +""" + +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import UnitLineUpgrade +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor +from ..tech_subprocessor import DE2TechSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup + + +def tech_group_to_tech(tech_group: GenieTechEffectBundleGroup) -> None: + """ + Creates raw API objects for a tech group. + + :param tech_group: Tech group that gets converted to a tech. + """ + tech_id = tech_group.get_id() + + # Skip Dark Age tech + if tech_id == 104: + return + + dataset = tech_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + # Start with the Tech object + tech_name = tech_lookup_dict[tech_id][0] + raw_api_object = RawAPIObject(tech_name, tech_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.tech.Tech") + + if isinstance(tech_group, UnitLineUpgrade): + unit_line = dataset.unit_lines[tech_group.get_line_id()] + head_unit_id = unit_line.get_head_unit_id() + obj_location = f"data/game_entity/generic/{name_lookup_dict[head_unit_id][1]}/" + + else: + obj_location = f"data/tech/generic/{tech_lookup_dict[tech_id][1]}/" + + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(tech_lookup_dict[tech_id][1]) + tech_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Types + # ======================================================================= + raw_api_object.add_raw_member("types", [], "engine.util.tech.Tech") + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{tech_name}.{tech_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{tech_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(tech_group, tech_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(tech_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.tech.Tech") + tech_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Description + # ======================================================================= + description_ref = f"{tech_name}.{tech_name}Description" + description_raw_api_object = RawAPIObject(description_ref, + f"{tech_name}Description", + dataset.nyan_api_objects) + description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + description_location = ForwardRef(tech_group, tech_name) + description_raw_api_object.set_location(description_location) + + description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + description_forward_ref = ForwardRef(tech_group, description_ref) + raw_api_object.add_raw_member("description", + description_forward_ref, + "engine.util.tech.Tech") + tech_group.add_raw_api_object(description_raw_api_object) + + # ======================================================================= + # Long description + # ======================================================================= + long_description_ref = f"{tech_name}.{tech_name}LongDescription" + long_description_raw_api_object = RawAPIObject(long_description_ref, + f"{tech_name}LongDescription", + dataset.nyan_api_objects) + long_description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + long_description_location = ForwardRef(tech_group, tech_name) + long_description_raw_api_object.set_location(long_description_location) + + long_description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + long_description_forward_ref = ForwardRef(tech_group, long_description_ref) + raw_api_object.add_raw_member("long_description", + long_description_forward_ref, + "engine.util.tech.Tech") + tech_group.add_raw_api_object(long_description_raw_api_object) + + # ======================================================================= + # Updates + # ======================================================================= + patches = [] + patches.extend(DE2TechSubprocessor.get_patches(tech_group)) + raw_api_object.add_raw_member("updates", patches, "engine.util.tech.Tech") + + # ======================================================================= + # Misc (Objects that are not used by the tech group itself, but use its values) + # ======================================================================= + if tech_group.is_researchable(): + AoCAuxiliarySubprocessor.get_researchable_tech(tech_group) diff --git a/openage/convert/processor/conversion/de2/nyan/terrain.py b/openage/convert/processor/conversion/de2/nyan/terrain.py new file mode 100644 index 0000000000..2f4dba124c --- /dev/null +++ b/openage/convert/processor/conversion/de2/nyan/terrain.py @@ -0,0 +1,213 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert terrain groups to openage terrains. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.combined_terrain import CombinedTerrain +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_terrain import GenieTerrainGroup + + +@staticmethod +def terrain_group_to_terrain(terrain_group: GenieTerrainGroup) -> None: + """ + Creates raw API objects for a terrain group. + + :param terrain_group: Terrain group that gets converted to a tech. + """ + terrain_index = terrain_group.get_id() + + dataset = terrain_group.data + + # name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) + terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( + dataset.game_version) + + if terrain_index not in terrain_lookup_dict: + # TODO: Not all terrains are used in DE2; filter out the unused terrains + # in pre-processor + return + + # Start with the Terrain object + terrain_name = terrain_lookup_dict[terrain_index][1] + raw_api_object = RawAPIObject(terrain_name, terrain_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.terrain.Terrain") + obj_location = f"data/terrain/{terrain_lookup_dict[terrain_index][2]}/" + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(terrain_lookup_dict[terrain_index][2]) + terrain_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Types + # ======================================================================= + terrain_types = [] + + for terrain_type in terrain_type_lookup_dict.values(): + if terrain_index in terrain_type[0]: + type_name = f"util.terrain_type.types.{terrain_type[2]}" + type_obj = dataset.pregen_nyan_objects[type_name].get_nyan_object() + terrain_types.append(type_obj) + + raw_api_object.add_raw_member("types", terrain_types, "engine.util.terrain.Terrain") + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{terrain_name}.{terrain_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{terrain_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(terrain_group, terrain_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(terrain_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.terrain.Terrain") + terrain_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Sound + # ======================================================================= + sound_name = f"{terrain_name}.Sound" + sound_raw_api_object = RawAPIObject(sound_name, "Sound", + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(terrain_group, terrain_name) + sound_raw_api_object.set_location(sound_location) + + # TODO: Sounds + sounds = [] + + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + sounds, + "engine.util.sound.Sound") + + sound_forward_ref = ForwardRef(terrain_group, sound_name) + raw_api_object.add_raw_member("sound", + sound_forward_ref, + "engine.util.terrain.Terrain") + + terrain_group.add_raw_api_object(sound_raw_api_object) + + # ======================================================================= + # Ambience + # ======================================================================= + terrain = terrain_group.get_terrain() + # ambients_count = terrain["terrain_units_used_count"].value + + ambience = [] + # TODO: Ambience +# =============================================================================== +# for ambient_index in range(ambients_count): +# ambient_id = terrain["terrain_unit_id"][ambient_index].value +# +# if ambient_id == -1: +# continue +# +# ambient_line = dataset.unit_ref[ambient_id] +# ambient_name = name_lookup_dict[ambient_line.get_head_unit_id()][0] +# +# ambient_ref = "%s.Ambient%s" % (terrain_name, str(ambient_index)) +# ambient_raw_api_object = RawAPIObject(ambient_ref, +# "Ambient%s" % (str(ambient_index)), +# dataset.nyan_api_objects) +# ambient_raw_api_object.add_raw_parent("engine.util.terrain.TerrainAmbient") +# ambient_location = ForwardRef(terrain_group, terrain_name) +# ambient_raw_api_object.set_location(ambient_location) +# +# # Game entity reference +# ambient_line_forward_ref = ForwardRef(ambient_line, ambient_name) +# ambient_raw_api_object.add_raw_member("object", +# ambient_line_forward_ref, +# "engine.util.terrain.TerrainAmbient") +# +# # Max density +# max_density = terrain["terrain_unit_density"][ambient_index].value +# ambient_raw_api_object.add_raw_member("max_density", +# max_density, +# "engine.util.terrain.TerrainAmbient") +# +# terrain_group.add_raw_api_object(ambient_raw_api_object) +# terrain_ambient_forward_ref = ForwardRef(terrain_group, ambient_ref) +# ambience.append(terrain_ambient_forward_ref) +# =============================================================================== + + raw_api_object.add_raw_member("ambience", ambience, "engine.util.terrain.Terrain") + + # ======================================================================= + # Path Costs + # ======================================================================= + path_costs = {} + restrictions = dataset.genie_terrain_restrictions + + # Land grid + path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() + land_restrictions = restrictions[0x07] + if land_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Water grid + path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() + water_restrictions = restrictions[0x03] + if water_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Air grid (default accessible) + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + path_costs[path_type] = 1 + + raw_api_object.add_raw_member("path_costs", path_costs, "engine.util.terrain.Terrain") + + # ======================================================================= + # Graphic + # ======================================================================= + texture_id = terrain.get_id() + + # Create animation object + graphic_name = f"{terrain_name}.TerrainTexture" + graphic_raw_api_object = RawAPIObject(graphic_name, "TerrainTexture", + dataset.nyan_api_objects) + graphic_raw_api_object.add_raw_parent("engine.util.graphics.Terrain") + graphic_location = ForwardRef(terrain_group, terrain_name) + graphic_raw_api_object.set_location(graphic_location) + + if texture_id in dataset.combined_terrains.keys(): + terrain_graphic = dataset.combined_terrains[texture_id] + + else: + terrain_graphic = CombinedTerrain(texture_id, + f"texture_{terrain_lookup_dict[terrain_index][2]}", + dataset) + dataset.combined_terrains.update({terrain_graphic.get_id(): terrain_graphic}) + + terrain_graphic.add_reference(graphic_raw_api_object) + + graphic_raw_api_object.add_raw_member("sprite", terrain_graphic, + "engine.util.graphics.Terrain") + + terrain_group.add_raw_api_object(graphic_raw_api_object) + graphic_forward_ref = ForwardRef(terrain_group, graphic_name) + raw_api_object.add_raw_member("terrain_graphic", graphic_forward_ref, + "engine.util.terrain.Terrain") diff --git a/openage/convert/processor/conversion/de2/nyan/unit.py b/openage/convert/processor/conversion/de2/nyan/unit.py new file mode 100644 index 0000000000..3c94f9ca6c --- /dev/null +++ b/openage/convert/processor/conversion/de2/nyan/unit.py @@ -0,0 +1,237 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert unit lines to openage game entities. +""" + +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ + GenieGarrisonMode, GenieMonkGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ...aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor +from ...aoc.modifier_subprocessor import AoCModifierSubprocessor +from ...aoc.nyan_subprocessor import AoCNyanSubprocessor +from ..ability_subprocessor import DE2AbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup + + +def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: + """ + Creates raw API objects for a unit line. + + :param unit_line: Unit line that gets converted to a game entity. + """ + current_unit = unit_line.get_head_unit() + current_unit_id = unit_line.get_head_unit_id() + + dataset = unit_line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[current_unit_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[current_unit_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[current_unit_id][1]) + unit_line.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give a unit two types + # - util.game_entity_type.types.Unit (if unit_type >= 70) + # - util.game_entity_type.types. (depending on the class) + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + unit_type = current_unit["unit_type"].value + + if unit_type >= 70: + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object( + ) + types_set.append(type_obj) + + unit_class = current_unit["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.los_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.move_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.resistance_ability(unit_line)) + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.stop_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.turn_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(unit_line)) + + # Creation + if len(unit_line.creates) > 0: + abilities_set.append(AoCAbilitySubprocessor.create_ability(unit_line)) + + # Config + ability = AoCAbilitySubprocessor.use_contingent_ability(unit_line) + if ability: + abilities_set.append(ability) + + if unit_line.get_head_unit_id() in (125, 692): + # Healing/Recharging attribute points (monks, berserks) + abilities_set.extend(DE2AbilitySubprocessor.regenerate_attribute_ability(unit_line)) + + # Applying effects and shooting projectiles + if unit_line.is_projectile_shooter(): + abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(unit_line, 7)) + AoCNyanSubprocessor.projectiles_from_line(unit_line) + + elif unit_line.is_melee() or unit_line.is_ranged(): + if unit_line.has_command(7): + # Attack + abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( + unit_line, + 7, + unit_line.is_ranged()) + + ) + + if unit_line.has_command(101): + # Build + abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 101, + unit_line.is_ranged()) + ) + + if unit_line.has_command(104): + # convert + abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( + unit_line, + 104, + unit_line.is_ranged()) + ) + + if unit_line.has_command(105): + # Heal + abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 105, + unit_line.is_ranged()) + ) + + if unit_line.has_command(106): + # Repair + abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 106, + unit_line.is_ranged()) + ) + + # Formation/Stance + if not isinstance(unit_line, GenieVillagerGroup): + abilities_set.append(AoCAbilitySubprocessor.formation_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(unit_line)) + + # Storage abilities + if unit_line.is_garrison(): + abilities_set.append(AoCAbilitySubprocessor.storage_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(unit_line)) + + garrison_mode = unit_line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.MONK: + abilities_set.append(AoCAbilitySubprocessor.collect_storage_ability(unit_line)) + + if len(unit_line.garrison_locations) > 0: + ability = AoCAbilitySubprocessor.enter_container_ability(unit_line) + if ability: + abilities_set.append(ability) + + ability = AoCAbilitySubprocessor.exit_container_ability(unit_line) + if ability: + abilities_set.append(ability) + + if isinstance(unit_line, GenieMonkGroup): + abilities_set.append(AoCAbilitySubprocessor.transfer_storage_ability(unit_line)) + + # Resource abilities + if unit_line.is_gatherer(): + abilities_set.append(AoCAbilitySubprocessor.drop_resources_ability(unit_line)) + abilities_set.extend(AoCAbilitySubprocessor.gather_ability(unit_line)) + + # Resource storage + if unit_line.is_gatherer() or unit_line.has_command(111): + abilities_set.append(AoCAbilitySubprocessor.resource_storage_ability(unit_line)) + + if isinstance(unit_line, GenieVillagerGroup): + # Farm restocking + abilities_set.append(AoCAbilitySubprocessor.restock_ability(unit_line, 50)) + + if unit_line.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(unit_line)) + + if unit_type == 70 and unit_line.get_class_id() not in (9, 10, 58): + # Excludes trebuchets and animals + abilities_set.append(AoCAbilitySubprocessor.herd_ability(unit_line)) + + if unit_line.get_class_id() == 58: + abilities_set.append(AoCAbilitySubprocessor.herdable_ability(unit_line)) + + # Trade abilities + if unit_line.has_command(111): + abilities_set.append(AoCAbilitySubprocessor.trade_ability(unit_line)) + + # ======================================================================= + # TODO: Transform + # ======================================================================= + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers_set = [] + + if unit_line.has_command(7) and not unit_line.is_projectile_shooter(): + modifiers_set.extend(AoCModifierSubprocessor.elevation_attack_modifiers(unit_line)) + + if unit_line.is_gatherer(): + modifiers_set.extend(AoCModifierSubprocessor.gather_rate_modifier(unit_line)) + + raw_api_object.add_raw_member("modifiers", modifiers_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Misc (Objects that are not used by the unit line itself, but use its values) + # ======================================================================= + if unit_line.is_creatable(): + AoCAuxiliarySubprocessor.get_creatable_game_entity(unit_line) diff --git a/openage/convert/processor/conversion/de2/nyan_subprocessor.py b/openage/convert/processor/conversion/de2/nyan_subprocessor.py index b688cc3e7c..81b52268b8 100644 --- a/openage/convert/processor/conversion/de2/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/de2/nyan_subprocessor.py @@ -1,9 +1,5 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. + """ Convert API-like objects to nyan objects. Subroutine of the main DE2 processor. @@ -11,29 +7,16 @@ from __future__ import annotations import typing -from ....entity_object.conversion.aoc.genie_tech import UnitLineUpgrade -from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ - GenieGarrisonMode, GenieMonkGroup -from ....entity_object.conversion.combined_terrain import CombinedTerrain -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ..aoc.ability_subprocessor import AoCAbilitySubprocessor -from ..aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor -from ..aoc.civ_subprocessor import AoCCivSubprocessor -from ..aoc.modifier_subprocessor import AoCModifierSubprocessor from ..aoc.nyan_subprocessor import AoCNyanSubprocessor -from .ability_subprocessor import DE2AbilitySubprocessor -from .civ_subprocessor import DE2CivSubprocessor -from .tech_subprocessor import DE2TechSubprocessor + +from .nyan.building import building_line_to_game_entity +from .nyan.civ import civ_group_to_civ +from .nyan.tech import tech_group_to_tech +from .nyan.terrain import terrain_group_to_terrain +from .nyan.unit import unit_line_to_game_entity if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup - from openage.convert.entity_object.conversion.aoc.genie_object_container import GenieObjectContainer - from openage.convert.entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup - from openage.convert.entity_object.conversion.aoc.genie_terrain import GenieTerrainGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup, \ - GenieBuildingLineGroup + from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer class DE2NyanSubprocessor: @@ -164,806 +147,8 @@ def _process_game_entities(cls, full_data_set: GenieObjectContainer) -> None: for civ_group in full_data_set.civ_groups.values(): cls.civ_group_to_civ(civ_group) - @staticmethod - def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: - """ - Creates raw API objects for a unit line. - - :param unit_line: Unit line that gets converted to a game entity. - :type unit_line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_unit = unit_line.get_head_unit() - current_unit_id = unit_line.get_head_unit_id() - - dataset = unit_line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[current_unit_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[current_unit_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[current_unit_id][1]) - unit_line.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give a unit two types - # - util.game_entity_type.types.Unit (if unit_type >= 70) - # - util.game_entity_type.types. (depending on the class) - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - unit_type = current_unit["unit_type"].value - - if unit_type >= 70: - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = current_unit["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.los_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.move_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.resistance_ability(unit_line)) - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.stop_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.turn_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(unit_line)) - - # Creation - if len(unit_line.creates) > 0: - abilities_set.append(AoCAbilitySubprocessor.create_ability(unit_line)) - - # Config - ability = AoCAbilitySubprocessor.use_contingent_ability(unit_line) - if ability: - abilities_set.append(ability) - - if unit_line.get_head_unit_id() in (125, 692): - # Healing/Recharging attribute points (monks, berserks) - abilities_set.extend(DE2AbilitySubprocessor.regenerate_attribute_ability(unit_line)) - - # Applying effects and shooting projectiles - if unit_line.is_projectile_shooter(): - abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(unit_line, 7)) - AoCNyanSubprocessor.projectiles_from_line(unit_line) - - elif unit_line.is_melee() or unit_line.is_ranged(): - if unit_line.has_command(7): - # Attack - abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability(unit_line, - 7, - unit_line.is_ranged())) - - if unit_line.has_command(101): - # Build - abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 101, - unit_line.is_ranged())) - - if unit_line.has_command(104): - # convert - abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability(unit_line, - 104, - unit_line.is_ranged())) - - if unit_line.has_command(105): - # Heal - abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 105, - unit_line.is_ranged())) - - if unit_line.has_command(106): - # Repair - abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 106, - unit_line.is_ranged())) - - # Formation/Stance - if not isinstance(unit_line, GenieVillagerGroup): - abilities_set.append(AoCAbilitySubprocessor.formation_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(unit_line)) - - # Storage abilities - if unit_line.is_garrison(): - abilities_set.append(AoCAbilitySubprocessor.storage_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(unit_line)) - - garrison_mode = unit_line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.MONK: - abilities_set.append(AoCAbilitySubprocessor.collect_storage_ability(unit_line)) - - if len(unit_line.garrison_locations) > 0: - ability = AoCAbilitySubprocessor.enter_container_ability(unit_line) - if ability: - abilities_set.append(ability) - - ability = AoCAbilitySubprocessor.exit_container_ability(unit_line) - if ability: - abilities_set.append(ability) - - if isinstance(unit_line, GenieMonkGroup): - abilities_set.append(AoCAbilitySubprocessor.transfer_storage_ability(unit_line)) - - # Resource abilities - if unit_line.is_gatherer(): - abilities_set.append(AoCAbilitySubprocessor.drop_resources_ability(unit_line)) - abilities_set.extend(AoCAbilitySubprocessor.gather_ability(unit_line)) - - # Resource storage - if unit_line.is_gatherer() or unit_line.has_command(111): - abilities_set.append(AoCAbilitySubprocessor.resource_storage_ability(unit_line)) - - if isinstance(unit_line, GenieVillagerGroup): - # Farm restocking - abilities_set.append(AoCAbilitySubprocessor.restock_ability(unit_line, 50)) - - if unit_line.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(unit_line)) - - if unit_type == 70 and unit_line.get_class_id() not in (9, 10, 58): - # Excludes trebuchets and animals - abilities_set.append(AoCAbilitySubprocessor.herd_ability(unit_line)) - - if unit_line.get_class_id() == 58: - abilities_set.append(AoCAbilitySubprocessor.herdable_ability(unit_line)) - - # Trade abilities - if unit_line.has_command(111): - abilities_set.append(AoCAbilitySubprocessor.trade_ability(unit_line)) - - # ======================================================================= - # TODO: Transform - # ======================================================================= - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers_set = [] - - if unit_line.has_command(7) and not unit_line.is_projectile_shooter(): - modifiers_set.extend(AoCModifierSubprocessor.elevation_attack_modifiers(unit_line)) - - if unit_line.is_gatherer(): - modifiers_set.extend(AoCModifierSubprocessor.gather_rate_modifier(unit_line)) - - raw_api_object.add_raw_member("modifiers", modifiers_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Misc (Objects that are not used by the unit line itself, but use its values) - # ======================================================================= - if unit_line.is_creatable(): - AoCAuxiliarySubprocessor.get_creatable_game_entity(unit_line) - - @staticmethod - def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: - """ - Creates raw API objects for a building line. - - :param building_line: Building line that gets converted to a game entity. - :type building_line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_building = building_line.line[0] - current_building_id = building_line.get_head_unit_id() - dataset = building_line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[current_building_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[current_building_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[current_building_id][1]) - building_line.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give a building two types - # - util.game_entity_type.types.Building (if unit_type >= 80) - # - util.game_entity_type.types. (depending on the class) - # and additionally - # - util.game_entity_type.types.DropSite (only if this is used as a drop site) - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - unit_type = current_building["unit_type"].value - - if unit_type >= 80: - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = current_building["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - if building_line.is_dropsite(): - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.DropSite"].get_nyan_object( - ) - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - abilities_set.append(AoCAbilitySubprocessor.attribute_change_tracker_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.death_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.resistance_ability(building_line)) - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.stop_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(building_line)) - - if building_line.get_head_unit()["speed"].value > 0: - abilities_set.append(AoCAbilitySubprocessor.move_ability(building_line)) - - # Config abilities - if building_line.is_creatable(): - abilities_set.append(AoCAbilitySubprocessor.constructable_ability(building_line)) - - if not building_line.is_passable(): - abilities_set.append(AoCAbilitySubprocessor.pathable_ability(building_line)) - - if building_line.has_foundation(): - if building_line.get_class_id() == 49: - # Use OverlayTerrain for the farm terrain - abilities_set.append(AoCAbilitySubprocessor.overlay_terrain_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line, - terrain_id=27)) - - else: - abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line)) - - # Creation/Research abilities - if len(building_line.creates) > 0: - abilities_set.append(AoCAbilitySubprocessor.create_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.production_queue_ability(building_line)) - - if len(building_line.researches) > 0: - abilities_set.append(AoCAbilitySubprocessor.research_ability(building_line)) - - # Effect abilities - if building_line.is_projectile_shooter(): - abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(building_line, 7)) - abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(building_line)) - AoCNyanSubprocessor.projectiles_from_line(building_line) - - # Storage abilities - if building_line.is_garrison(): - abilities_set.append(AoCAbilitySubprocessor.storage_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(building_line)) - - garrison_mode = building_line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.NATURAL: - abilities_set.append( - AoCAbilitySubprocessor.send_back_to_task_ability(building_line)) - - if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): - abilities_set.append(AoCAbilitySubprocessor.rally_point_ability(building_line)) - - # Resource abilities - if building_line.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(building_line)) - - if building_line.is_dropsite(): - abilities_set.append(AoCAbilitySubprocessor.drop_site_ability(building_line)) - - ability = AoCAbilitySubprocessor.provide_contingent_ability(building_line) - if ability: - abilities_set.append(ability) - - # Trade abilities - if building_line.is_trade_post(): - abilities_set.append(AoCAbilitySubprocessor.trade_post_ability(building_line)) - - if building_line.get_id() == 84: - # Market trading - abilities_set.extend(AoCAbilitySubprocessor.exchange_resources_ability(building_line)) - - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - raw_api_object.add_raw_member("modifiers", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Misc (Objects that are not used by the unit line itself, but use its values) - # ======================================================================= - if building_line.is_creatable(): - AoCAuxiliarySubprocessor.get_creatable_game_entity(building_line) - - @staticmethod - def tech_group_to_tech(tech_group: GenieTechEffectBundleGroup) -> None: - """ - Creates raw API objects for a tech group. - - :param tech_group: Tech group that gets converted to a tech. - :type tech_group: ..dataformat.converter_object.ConverterObjectGroup - """ - tech_id = tech_group.get_id() - - # Skip Dark Age tech - if tech_id == 104: - return - - dataset = tech_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - # Start with the Tech object - tech_name = tech_lookup_dict[tech_id][0] - raw_api_object = RawAPIObject(tech_name, tech_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.tech.Tech") - - if isinstance(tech_group, UnitLineUpgrade): - unit_line = dataset.unit_lines[tech_group.get_line_id()] - head_unit_id = unit_line.get_head_unit_id() - obj_location = f"data/game_entity/generic/{name_lookup_dict[head_unit_id][1]}/" - - else: - obj_location = f"data/tech/generic/{tech_lookup_dict[tech_id][1]}/" - - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(tech_lookup_dict[tech_id][1]) - tech_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Types - # ======================================================================= - raw_api_object.add_raw_member("types", [], "engine.util.tech.Tech") - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{tech_name}.{tech_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{tech_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(tech_group, tech_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(tech_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.tech.Tech") - tech_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Description - # ======================================================================= - description_ref = f"{tech_name}.{tech_name}Description" - description_raw_api_object = RawAPIObject(description_ref, - f"{tech_name}Description", - dataset.nyan_api_objects) - description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - description_location = ForwardRef(tech_group, tech_name) - description_raw_api_object.set_location(description_location) - - description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - description_forward_ref = ForwardRef(tech_group, description_ref) - raw_api_object.add_raw_member("description", - description_forward_ref, - "engine.util.tech.Tech") - tech_group.add_raw_api_object(description_raw_api_object) - - # ======================================================================= - # Long description - # ======================================================================= - long_description_ref = f"{tech_name}.{tech_name}LongDescription" - long_description_raw_api_object = RawAPIObject(long_description_ref, - f"{tech_name}LongDescription", - dataset.nyan_api_objects) - long_description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - long_description_location = ForwardRef(tech_group, tech_name) - long_description_raw_api_object.set_location(long_description_location) - - long_description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - long_description_forward_ref = ForwardRef(tech_group, long_description_ref) - raw_api_object.add_raw_member("long_description", - long_description_forward_ref, - "engine.util.tech.Tech") - tech_group.add_raw_api_object(long_description_raw_api_object) - - # ======================================================================= - # Updates - # ======================================================================= - patches = [] - patches.extend(DE2TechSubprocessor.get_patches(tech_group)) - raw_api_object.add_raw_member("updates", patches, "engine.util.tech.Tech") - - # ======================================================================= - # Misc (Objects that are not used by the tech group itself, but use its values) - # ======================================================================= - if tech_group.is_researchable(): - AoCAuxiliarySubprocessor.get_researchable_tech(tech_group) - - @staticmethod - def terrain_group_to_terrain(terrain_group: GenieTerrainGroup) -> None: - """ - Creates raw API objects for a terrain group. - - :param terrain_group: Terrain group that gets converted to a tech. - :type terrain_group: ..dataformat.converter_object.ConverterObjectGroup - """ - terrain_index = terrain_group.get_id() - - dataset = terrain_group.data - - # name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) - terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( - dataset.game_version) - - if terrain_index not in terrain_lookup_dict: - # TODO: Not all terrains are used in DE2; filter out the unused terrains - # in pre-processor - return - - # Start with the Terrain object - terrain_name = terrain_lookup_dict[terrain_index][1] - raw_api_object = RawAPIObject(terrain_name, terrain_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.terrain.Terrain") - obj_location = f"data/terrain/{terrain_lookup_dict[terrain_index][2]}/" - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(terrain_lookup_dict[terrain_index][2]) - terrain_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Types - # ======================================================================= - terrain_types = [] - - for terrain_type in terrain_type_lookup_dict.values(): - if terrain_index in terrain_type[0]: - type_name = f"util.terrain_type.types.{terrain_type[2]}" - type_obj = dataset.pregen_nyan_objects[type_name].get_nyan_object() - terrain_types.append(type_obj) - - raw_api_object.add_raw_member("types", terrain_types, "engine.util.terrain.Terrain") - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{terrain_name}.{terrain_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{terrain_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(terrain_group, terrain_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(terrain_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.terrain.Terrain") - terrain_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Sound - # ======================================================================= - sound_name = f"{terrain_name}.Sound" - sound_raw_api_object = RawAPIObject(sound_name, "Sound", - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(terrain_group, terrain_name) - sound_raw_api_object.set_location(sound_location) - - # TODO: Sounds - sounds = [] - - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - sounds, - "engine.util.sound.Sound") - - sound_forward_ref = ForwardRef(terrain_group, sound_name) - raw_api_object.add_raw_member("sound", - sound_forward_ref, - "engine.util.terrain.Terrain") - - terrain_group.add_raw_api_object(sound_raw_api_object) - - # ======================================================================= - # Ambience - # ======================================================================= - terrain = terrain_group.get_terrain() - # ambients_count = terrain["terrain_units_used_count"].value - - ambience = [] - # TODO: Ambience -# =============================================================================== -# for ambient_index in range(ambients_count): -# ambient_id = terrain["terrain_unit_id"][ambient_index].value -# -# if ambient_id == -1: -# continue -# -# ambient_line = dataset.unit_ref[ambient_id] -# ambient_name = name_lookup_dict[ambient_line.get_head_unit_id()][0] -# -# ambient_ref = "%s.Ambient%s" % (terrain_name, str(ambient_index)) -# ambient_raw_api_object = RawAPIObject(ambient_ref, -# "Ambient%s" % (str(ambient_index)), -# dataset.nyan_api_objects) -# ambient_raw_api_object.add_raw_parent("engine.util.terrain.TerrainAmbient") -# ambient_location = ForwardRef(terrain_group, terrain_name) -# ambient_raw_api_object.set_location(ambient_location) -# -# # Game entity reference -# ambient_line_forward_ref = ForwardRef(ambient_line, ambient_name) -# ambient_raw_api_object.add_raw_member("object", -# ambient_line_forward_ref, -# "engine.util.terrain.TerrainAmbient") -# -# # Max density -# max_density = terrain["terrain_unit_density"][ambient_index].value -# ambient_raw_api_object.add_raw_member("max_density", -# max_density, -# "engine.util.terrain.TerrainAmbient") -# -# terrain_group.add_raw_api_object(ambient_raw_api_object) -# terrain_ambient_forward_ref = ForwardRef(terrain_group, ambient_ref) -# ambience.append(terrain_ambient_forward_ref) -# =============================================================================== - - raw_api_object.add_raw_member("ambience", ambience, "engine.util.terrain.Terrain") - - # ======================================================================= - # Path Costs - # ======================================================================= - path_costs = {} - restrictions = dataset.genie_terrain_restrictions - - # Land grid - path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() - land_restrictions = restrictions[0x07] - if land_restrictions.is_accessible(terrain_index): - path_costs[path_type] = 1 - - else: - path_costs[path_type] = 255 - - # Water grid - path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() - water_restrictions = restrictions[0x03] - if water_restrictions.is_accessible(terrain_index): - path_costs[path_type] = 1 - - else: - path_costs[path_type] = 255 - - # Air grid (default accessible) - path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() - path_costs[path_type] = 1 - - raw_api_object.add_raw_member("path_costs", path_costs, "engine.util.terrain.Terrain") - - # ======================================================================= - # Graphic - # ======================================================================= - texture_id = terrain.get_id() - - # Create animation object - graphic_name = f"{terrain_name}.TerrainTexture" - graphic_raw_api_object = RawAPIObject(graphic_name, "TerrainTexture", - dataset.nyan_api_objects) - graphic_raw_api_object.add_raw_parent("engine.util.graphics.Terrain") - graphic_location = ForwardRef(terrain_group, terrain_name) - graphic_raw_api_object.set_location(graphic_location) - - if texture_id in dataset.combined_terrains.keys(): - terrain_graphic = dataset.combined_terrains[texture_id] - - else: - terrain_graphic = CombinedTerrain(texture_id, - f"texture_{terrain_lookup_dict[terrain_index][2]}", - dataset) - dataset.combined_terrains.update({terrain_graphic.get_id(): terrain_graphic}) - - terrain_graphic.add_reference(graphic_raw_api_object) - - graphic_raw_api_object.add_raw_member("sprite", terrain_graphic, - "engine.util.graphics.Terrain") - - terrain_group.add_raw_api_object(graphic_raw_api_object) - graphic_forward_ref = ForwardRef(terrain_group, graphic_name) - raw_api_object.add_raw_member("terrain_graphic", graphic_forward_ref, - "engine.util.terrain.Terrain") - - @staticmethod - def civ_group_to_civ(civ_group: GenieCivilizationGroup) -> None: - """ - Creates raw API objects for a civ group. - - :param civ_group: Terrain group that gets converted to a tech. - :type civ_group: ..dataformat.converter_object.ConverterObjectGroup - """ - civ_id = civ_group.get_id() - - dataset = civ_group.data - - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - # Start with the Tech object - tech_name = civ_lookup_dict[civ_id][0] - raw_api_object = RawAPIObject(tech_name, tech_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.setup.PlayerSetup") - - obj_location = f"data/civ/{civ_lookup_dict[civ_id][1]}/" - - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(civ_lookup_dict[civ_id][1]) - civ_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{tech_name}.{tech_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{tech_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(civ_group, tech_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(civ_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Description - # ======================================================================= - description_ref = f"{tech_name}.{tech_name}Description" - description_raw_api_object = RawAPIObject(description_ref, - f"{tech_name}Description", - dataset.nyan_api_objects) - description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - description_location = ForwardRef(civ_group, tech_name) - description_raw_api_object.set_location(description_location) - - description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - description_forward_ref = ForwardRef(civ_group, description_ref) - raw_api_object.add_raw_member("description", - description_forward_ref, - "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(description_raw_api_object) - - # ======================================================================= - # Long description - # ======================================================================= - long_description_ref = f"{tech_name}.{tech_name}LongDescription" - long_description_raw_api_object = RawAPIObject(long_description_ref, - f"{tech_name}LongDescription", - dataset.nyan_api_objects) - long_description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - long_description_location = ForwardRef(civ_group, tech_name) - long_description_raw_api_object.set_location(long_description_location) - - long_description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - long_description_forward_ref = ForwardRef(civ_group, long_description_ref) - raw_api_object.add_raw_member("long_description", - long_description_forward_ref, - "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(long_description_raw_api_object) - - # ======================================================================= - # TODO: Leader names - # ======================================================================= - raw_api_object.add_raw_member("leader_names", - [], - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers = AoCCivSubprocessor.get_modifiers(civ_group) - raw_api_object.add_raw_member("modifiers", - modifiers, - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Starting resources - # ======================================================================= - resource_amounts = AoCCivSubprocessor.get_starting_resources(civ_group) - raw_api_object.add_raw_member("starting_resources", - resource_amounts, - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Game setup - # ======================================================================= - game_setup = DE2CivSubprocessor.get_civ_setup(civ_group) - raw_api_object.add_raw_member("game_setup", - game_setup, - "engine.util.setup.PlayerSetup") + building_line_to_game_entity = staticmethod(building_line_to_game_entity) + civ_group_to_civ = staticmethod(civ_group_to_civ) + tech_group_to_tech = staticmethod(tech_group_to_tech) + terrain_group_to_terrain = staticmethod(terrain_group_to_terrain) + unit_line_to_game_entity = staticmethod(unit_line_to_game_entity) From 724e488ec5c516f80c220b65a2ee53887345df80 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 16:23:48 +0200 Subject: [PATCH 129/163] convert: Refactor DE2Processor into separate files. --- .../processor/conversion/de2/CMakeLists.txt | 1 + .../conversion/de2/main/CMakeLists.txt | 6 + .../processor/conversion/de2/main/__init__.py | 5 + .../de2/main/extract/CMakeLists.txt | 5 + .../conversion/de2/main/extract/__init__.py | 5 + .../conversion/de2/main/extract/graphics.py | 42 ++++ .../conversion/de2/main/extract/unit.py | 62 ++++++ .../conversion/de2/main/groups/CMakeLists.txt | 6 + .../conversion/de2/main/groups/__init__.py | 5 + .../de2/main/groups/ambient_group.py | 36 ++++ .../de2/main/groups/building_line.py | 32 ++++ .../de2/main/groups/variant_group.py | 36 ++++ .../processor/conversion/de2/processor.py | 181 ++---------------- 13 files changed, 261 insertions(+), 161 deletions(-) create mode 100644 openage/convert/processor/conversion/de2/main/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/main/__init__.py create mode 100644 openage/convert/processor/conversion/de2/main/extract/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/main/extract/__init__.py create mode 100644 openage/convert/processor/conversion/de2/main/extract/graphics.py create mode 100644 openage/convert/processor/conversion/de2/main/extract/unit.py create mode 100644 openage/convert/processor/conversion/de2/main/groups/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/main/groups/__init__.py create mode 100644 openage/convert/processor/conversion/de2/main/groups/ambient_group.py create mode 100644 openage/convert/processor/conversion/de2/main/groups/building_line.py create mode 100644 openage/convert/processor/conversion/de2/main/groups/variant_group.py diff --git a/openage/convert/processor/conversion/de2/CMakeLists.txt b/openage/convert/processor/conversion/de2/CMakeLists.txt index 04691ba629..12d82fcb19 100644 --- a/openage/convert/processor/conversion/de2/CMakeLists.txt +++ b/openage/convert/processor/conversion/de2/CMakeLists.txt @@ -13,5 +13,6 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(civ) +add_subdirectory(main) add_subdirectory(media) add_subdirectory(nyan) diff --git a/openage/convert/processor/conversion/de2/main/CMakeLists.txt b/openage/convert/processor/conversion/de2/main/CMakeLists.txt new file mode 100644 index 0000000000..aa8330df4c --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/CMakeLists.txt @@ -0,0 +1,6 @@ +add_py_modules( + __init__.py +) + +add_subdirectory(extract) +add_subdirectory(groups) diff --git a/openage/convert/processor/conversion/de2/main/__init__.py b/openage/convert/processor/conversion/de2/main/__init__.py new file mode 100644 index 0000000000..c2634d8c05 --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Routines for the main DE2 conversion process. +""" diff --git a/openage/convert/processor/conversion/de2/main/extract/CMakeLists.txt b/openage/convert/processor/conversion/de2/main/extract/CMakeLists.txt new file mode 100644 index 0000000000..962c2954b0 --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/extract/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py + graphics.py + unit.py +) diff --git a/openage/convert/processor/conversion/de2/main/extract/__init__.py b/openage/convert/processor/conversion/de2/main/extract/__init__.py new file mode 100644 index 0000000000..f3269f5b7d --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/extract/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract DE2 data from the game dataset and prepares it for conversion. +""" diff --git a/openage/convert/processor/conversion/de2/main/extract/graphics.py b/openage/convert/processor/conversion/de2/main/extract/graphics.py new file mode 100644 index 0000000000..fbe5c5ae91 --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/extract/graphics.py @@ -0,0 +1,42 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract graphics from the DE2 data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_graphic import GenieGraphic +from ......value_object.read.value_members import ArrayMember + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_genie_graphics(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract graphic definitions from the game data. + + :param gamespec: Gamedata from empires.dat file. + """ + # call hierarchy: wrapper[0]->graphics + raw_graphics = gamespec[0]["graphics"].value + + for raw_graphic in raw_graphics: + # Can be ignored if there is no filename associated + filename = raw_graphic["filename"].value.lower() + if not filename: + continue + + graphic_id = raw_graphic["graphic_id"].value + graphic_members = raw_graphic.value + graphic = GenieGraphic(graphic_id, full_data_set, members=graphic_members) + + if filename not in full_data_set.existing_graphics: + graphic.exists = False + + full_data_set.genie_graphics.update({graphic.get_id(): graphic}) + + # Detect subgraphics + for genie_graphic in full_data_set.genie_graphics.values(): + genie_graphic.detect_subgraphics() diff --git a/openage/convert/processor/conversion/de2/main/extract/unit.py b/openage/convert/processor/conversion/de2/main/extract/unit.py new file mode 100644 index 0000000000..df28ef9951 --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/extract/unit.py @@ -0,0 +1,62 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract units from the DE2 data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieUnitObject +from ......value_object.read.value_members import ArrayMember, StorageType + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def extract_genie_units(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract units from the game data. + + :param gamespec: Gamedata from empires.dat file. + """ + # Units are stored in the civ container. + # All civs point to the same units (?) except for Gaia which has more. + # Gaia also seems to have the most units, so we only read from Gaia + # + # call hierarchy: wrapper[0]->civs[0]->units + raw_units = gamespec[0]["civs"][0]["units"].value + + # Unit headers store the things units can do + raw_unit_headers = gamespec[0]["unit_headers"].value + + for raw_unit in raw_units: + unit_id = raw_unit["id0"].value + unit_members = raw_unit.value + + # Turn attack and armor into containers to make diffing work + if "attacks" in unit_members.keys(): + attacks_member = unit_members.pop("attacks") + attacks_member = attacks_member.get_container("type_id") + armors_member = unit_members.pop("armors") + armors_member = armors_member.get_container("type_id") + + unit_members.update({"attacks": attacks_member}) + unit_members.update({"armors": armors_member}) + + unit = GenieUnitObject(unit_id, full_data_set, members=unit_members) + full_data_set.genie_units.update({unit.get_id(): unit}) + + # Commands + if "unit_commands" not in unit_members.keys(): + # Only ActionUnits with type >= 40 should have commands + unit_type = raw_unit["unit_type"].value + if unit_type >= 40: + unit_commands = raw_unit_headers[unit_id]["unit_commands"] + unit.add_member(unit_commands) + + else: + # Create empty member if no headers are present + unit_commands = ArrayMember("unit_commands", + StorageType.CONTAINER_MEMBER, + members=[]) + unit.add_member(unit_commands) diff --git a/openage/convert/processor/conversion/de2/main/groups/CMakeLists.txt b/openage/convert/processor/conversion/de2/main/groups/CMakeLists.txt new file mode 100644 index 0000000000..61e0cc6d97 --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/groups/CMakeLists.txt @@ -0,0 +1,6 @@ +add_py_modules( + __init__.py + ambient_group.py + building_line.py + variant_group.py +) diff --git a/openage/convert/processor/conversion/de2/main/groups/__init__.py b/openage/convert/processor/conversion/de2/main/groups/__init__.py new file mode 100644 index 0000000000..79c6de4201 --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/groups/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create lines and groups fron extracted DE2 data. +""" diff --git a/openage/convert/processor/conversion/de2/main/groups/ambient_group.py b/openage/convert/processor/conversion/de2/main/groups/ambient_group.py new file mode 100644 index 0000000000..0f0b5984d2 --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/groups/ambient_group.py @@ -0,0 +1,36 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create ambient groups from genie units. +""" +from __future__ import annotations +import typing + +import openage.convert.value_object.conversion.aoc.internal_nyan_names as aoc_internal +import openage.convert.value_object.conversion.de2.internal_nyan_names as de2_internal + +from .......util.ordered_set import OrderedSet +from ......entity_object.conversion.aoc.genie_unit import GenieAmbientGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_ambient_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create ambient groups, mostly for resources and scenery. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + ambient_ids = OrderedSet() + ambient_ids.update(aoc_internal.AMBIENT_GROUP_LOOKUPS.keys()) + ambient_ids.update(de2_internal.AMBIENT_GROUP_LOOKUPS.keys()) + genie_units = full_data_set.genie_units + + for ambient_id in ambient_ids: + ambient_group = GenieAmbientGroup(ambient_id, full_data_set) + ambient_group.add_unit(genie_units[ambient_id]) + full_data_set.ambient_groups.update({ambient_group.get_id(): ambient_group}) + full_data_set.unit_ref.update({ambient_id: ambient_group}) diff --git a/openage/convert/processor/conversion/de2/main/groups/building_line.py b/openage/convert/processor/conversion/de2/main/groups/building_line.py new file mode 100644 index 0000000000..f0680d6ded --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/groups/building_line.py @@ -0,0 +1,32 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create building lines from genie buildings. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_extra_building_lines(full_data_set: GenieObjectContainer) -> None: + """ + Create additional units that are not in the building connections. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + extra_units = ( + 1734, # Folwark + 1808, # Mule Cart + ) + + for unit_id in extra_units: + building_line = GenieBuildingLineGroup(unit_id, full_data_set) + building_line.add_unit(full_data_set.genie_units[unit_id]) + full_data_set.building_lines.update({building_line.get_id(): building_line}) + full_data_set.unit_ref.update({unit_id: building_line}) diff --git a/openage/convert/processor/conversion/de2/main/groups/variant_group.py b/openage/convert/processor/conversion/de2/main/groups/variant_group.py new file mode 100644 index 0000000000..f3682b32ee --- /dev/null +++ b/openage/convert/processor/conversion/de2/main/groups/variant_group.py @@ -0,0 +1,36 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create variant groups from genie units. +""" +from __future__ import annotations +import typing + +import openage.convert.value_object.conversion.aoc.internal_nyan_names as aoc_internal +import openage.convert.value_object.conversion.de2.internal_nyan_names as de2_internal + +from ......entity_object.conversion.aoc.genie_unit import GenieVariantGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_variant_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create variant groups. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + variants = {} + variants.update(aoc_internal.VARIANT_GROUP_LOOKUPS) + variants.update(de2_internal.VARIANT_GROUP_LOOKUPS) + + for group_id, variant in variants.items(): + variant_group = GenieVariantGroup(group_id, full_data_set) + full_data_set.variant_groups.update({variant_group.get_id(): variant_group}) + + for variant_id in variant[2]: + variant_group.add_unit(full_data_set.genie_units[variant_id]) + full_data_set.unit_ref.update({variant_id: variant_group}) diff --git a/openage/convert/processor/conversion/de2/processor.py b/openage/convert/processor/conversion/de2/processor.py index 19001b9087..80c31f3772 100644 --- a/openage/convert/processor/conversion/de2/processor.py +++ b/openage/convert/processor/conversion/de2/processor.py @@ -1,37 +1,33 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=line-too-long,too-many-lines,too-many-branches,too-many-statements +# Copyright 2020-2025 the openage authors. See copying.md for legal info. + """ Convert data from DE2 to openage formats. """ from __future__ import annotations import typing - -from openage.convert.value_object.read.value_members import ArrayMember, StorageType -import openage.convert.value_object.conversion.aoc.internal_nyan_names as aoc_internal -import openage.convert.value_object.conversion.de2.internal_nyan_names as de2_internal - from .....log import info -from .....util.ordered_set import OrderedSet -from ....entity_object.conversion.aoc.genie_graphic import GenieGraphic from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer -from ....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, GenieUnitObject, GenieAmbientGroup, \ - GenieVariantGroup -from ....service.debug_info import debug_converter_objects, \ - debug_converter_object_groups +from ....service.debug_info import debug_converter_objects, debug_converter_object_groups from ....service.read.nyan_api_loader import load_api +from ....value_object.read.value_members import ArrayMember from ..aoc.pregen_processor import AoCPregenSubprocessor from ..aoc.processor import AoCProcessor from .media_subprocessor import DE2MediaSubprocessor from .modpack_subprocessor import DE2ModpackSubprocessor from .nyan_subprocessor import DE2NyanSubprocessor +from .main.extract.graphics import extract_genie_graphics +from .main.extract.unit import extract_genie_units +from .main.groups.ambient_group import create_ambient_groups +from .main.groups.variant_group import create_variant_groups +from .main.groups.building_line import create_extra_building_lines + if typing.TYPE_CHECKING: from argparse import Namespace - from openage.convert.entity_object.conversion.stringresource import StringResource - from openage.convert.entity_object.conversion.modpack import Modpack - from openage.convert.value_object.init.game_version import GameVersion + from ....entity_object.conversion.stringresource import StringResource + from ....entity_object.conversion.modpack import Modpack + from ....value_object.init.game_version import GameVersion class DE2Processor: @@ -117,6 +113,9 @@ def _pre_processor( return dataset + extract_genie_graphics = staticmethod(extract_genie_graphics) + extract_genie_units = staticmethod(extract_genie_units) + @classmethod def _processor(cls, full_data_set: GenieObjectContainer) -> GenieObjectContainer: """ @@ -159,6 +158,10 @@ def _processor(cls, full_data_set: GenieObjectContainer) -> GenieObjectContainer return full_data_set + create_ambient_groups = staticmethod(create_ambient_groups) + create_variant_groups = staticmethod(create_variant_groups) + create_extra_building_lines = staticmethod(create_extra_building_lines) + @classmethod def _post_processor(cls, full_data_set: GenieObjectContainer) -> list[Modpack]: """ @@ -178,147 +181,3 @@ def _post_processor(cls, full_data_set: GenieObjectContainer) -> list[Modpack]: DE2MediaSubprocessor.convert(full_data_set) return DE2ModpackSubprocessor.get_modpacks(full_data_set) - - @staticmethod - def extract_genie_units(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract units from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # Units are stored in the civ container. - # All civs point to the same units (?) except for Gaia which has more. - # Gaia also seems to have the most units, so we only read from Gaia - # - # call hierarchy: wrapper[0]->civs[0]->units - raw_units = gamespec[0]["civs"][0]["units"].value - - # Unit headers store the things units can do - raw_unit_headers = gamespec[0]["unit_headers"].value - - for raw_unit in raw_units: - unit_id = raw_unit["id0"].value - unit_members = raw_unit.value - - # Turn attack and armor into containers to make diffing work - if "attacks" in unit_members.keys(): - attacks_member = unit_members.pop("attacks") - attacks_member = attacks_member.get_container("type_id") - armors_member = unit_members.pop("armors") - armors_member = armors_member.get_container("type_id") - - unit_members.update({"attacks": attacks_member}) - unit_members.update({"armors": armors_member}) - - unit = GenieUnitObject(unit_id, full_data_set, members=unit_members) - full_data_set.genie_units.update({unit.get_id(): unit}) - - # Commands - if "unit_commands" not in unit_members.keys(): - # Only ActionUnits with type >= 40 should have commands - unit_type = raw_unit["unit_type"].value - if unit_type >= 40: - unit_commands = raw_unit_headers[unit_id]["unit_commands"] - unit.add_member(unit_commands) - - else: - # Create empty member if no headers are present - unit_commands = ArrayMember("unit_commands", - StorageType.CONTAINER_MEMBER, - members=[]) - unit.add_member(unit_commands) - - @staticmethod - def extract_genie_graphics(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract graphic definitions from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->graphics - raw_graphics = gamespec[0]["graphics"].value - - for raw_graphic in raw_graphics: - # Can be ignored if there is no filename associated - filename = raw_graphic["filename"].value.lower() - if not filename: - continue - - graphic_id = raw_graphic["graphic_id"].value - graphic_members = raw_graphic.value - graphic = GenieGraphic(graphic_id, full_data_set, members=graphic_members) - - if filename not in full_data_set.existing_graphics: - graphic.exists = False - - full_data_set.genie_graphics.update({graphic.get_id(): graphic}) - - # Detect subgraphics - for genie_graphic in full_data_set.genie_graphics.values(): - genie_graphic.detect_subgraphics() - - @staticmethod - def create_ambient_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create ambient groups, mostly for resources and scenery. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - ambient_ids = OrderedSet() - ambient_ids.update(aoc_internal.AMBIENT_GROUP_LOOKUPS.keys()) - ambient_ids.update(de2_internal.AMBIENT_GROUP_LOOKUPS.keys()) - genie_units = full_data_set.genie_units - - for ambient_id in ambient_ids: - ambient_group = GenieAmbientGroup(ambient_id, full_data_set) - ambient_group.add_unit(genie_units[ambient_id]) - full_data_set.ambient_groups.update({ambient_group.get_id(): ambient_group}) - full_data_set.unit_ref.update({ambient_id: ambient_group}) - - @staticmethod - def create_variant_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create variant groups. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - variants = {} - variants.update(aoc_internal.VARIANT_GROUP_LOOKUPS) - variants.update(de2_internal.VARIANT_GROUP_LOOKUPS) - - for group_id, variant in variants.items(): - variant_group = GenieVariantGroup(group_id, full_data_set) - full_data_set.variant_groups.update({variant_group.get_id(): variant_group}) - - for variant_id in variant[2]: - variant_group.add_unit(full_data_set.genie_units[variant_id]) - full_data_set.unit_ref.update({variant_id: variant_group}) - - @staticmethod - def create_extra_building_lines(full_data_set: GenieObjectContainer) -> None: - """ - Create additional units that are not in the building connections. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - extra_units = ( - 1734, # Folwark - 1808, # Mule Cart - ) - - for unit_id in extra_units: - building_line = GenieBuildingLineGroup(unit_id, full_data_set) - building_line.add_unit(full_data_set.genie_units[unit_id]) - full_data_set.building_lines.update({building_line.get_id(): building_line}) - full_data_set.unit_ref.update({unit_id: building_line}) From b949c05d5241ad7d80b84e2fe85748ef8c60bd08 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 16:34:58 +0200 Subject: [PATCH 130/163] convert: Refactor DE2TechSubprocessor into separate files. --- .../processor/conversion/de2/CMakeLists.txt | 1 + .../conversion/de2/tech/CMakeLists.txt | 6 + .../processor/conversion/de2/tech/__init__.py | 5 + .../conversion/de2/tech/attribute_modify.py | 83 ++++++ .../conversion/de2/tech/resource_modify.py | 58 ++++ .../conversion/de2/tech/upgrade_funcs.py | 153 ++++++++++ .../conversion/de2/tech_subprocessor.py | 270 +----------------- 7 files changed, 315 insertions(+), 261 deletions(-) create mode 100644 openage/convert/processor/conversion/de2/tech/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/tech/__init__.py create mode 100644 openage/convert/processor/conversion/de2/tech/attribute_modify.py create mode 100644 openage/convert/processor/conversion/de2/tech/resource_modify.py create mode 100644 openage/convert/processor/conversion/de2/tech/upgrade_funcs.py diff --git a/openage/convert/processor/conversion/de2/CMakeLists.txt b/openage/convert/processor/conversion/de2/CMakeLists.txt index 12d82fcb19..390c96b2ab 100644 --- a/openage/convert/processor/conversion/de2/CMakeLists.txt +++ b/openage/convert/processor/conversion/de2/CMakeLists.txt @@ -16,3 +16,4 @@ add_subdirectory(civ) add_subdirectory(main) add_subdirectory(media) add_subdirectory(nyan) +add_subdirectory(tech) diff --git a/openage/convert/processor/conversion/de2/tech/CMakeLists.txt b/openage/convert/processor/conversion/de2/tech/CMakeLists.txt new file mode 100644 index 0000000000..7e768537fe --- /dev/null +++ b/openage/convert/processor/conversion/de2/tech/CMakeLists.txt @@ -0,0 +1,6 @@ +add_py_modules( + __init__.py + attribute_modify.py + resource_modify.py + upgrade_funcs.py +) diff --git a/openage/convert/processor/conversion/de2/tech/__init__.py b/openage/convert/processor/conversion/de2/tech/__init__.py new file mode 100644 index 0000000000..b09a18404c --- /dev/null +++ b/openage/convert/processor/conversion/de2/tech/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create nyan patches for techs in DE2. +""" diff --git a/openage/convert/processor/conversion/de2/tech/attribute_modify.py b/openage/convert/processor/conversion/de2/tech/attribute_modify.py new file mode 100644 index 0000000000..90f90610ac --- /dev/null +++ b/openage/convert/processor/conversion/de2/tech/attribute_modify.py @@ -0,0 +1,83 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying attributes of entities. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .upgrade_funcs import UPGRADE_ATTRIBUTE_FUNCS + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + from .....value_object.conversion.forward_ref import ForwardRef + + +def attribute_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying attributes of entities. + """ + patches = [] + dataset = converter_group.data + + effect_type = effect.get_type() + operator = None + if effect_type in (0, 10): + operator = MemberOperator.ASSIGN + + elif effect_type in (4, 14): + operator = MemberOperator.ADD + + elif effect_type in (5, 15): + operator = MemberOperator.MULTIPLY + + else: + raise TypeError(f"Effect type {effect_type} is not a valid attribute effect") + + unit_id = effect["attr_a"].value + class_id = effect["attr_b"].value + attribute_type = effect["attr_c"].value + value = effect["attr_d"].value + + if attribute_type == -1: + return patches + + affected_entities = [] + if unit_id != -1: + entity_lines = {} + entity_lines.update(dataset.unit_lines) + entity_lines.update(dataset.building_lines) + entity_lines.update(dataset.ambient_groups) + + for line in entity_lines.values(): + if line.contains_entity(unit_id): + affected_entities.append(line) + + elif attribute_type == 19: + if line.is_projectile_shooter() and line.has_projectile(unit_id): + affected_entities.append(line) + + elif class_id != -1: + entity_lines = {} + entity_lines.update(dataset.unit_lines) + entity_lines.update(dataset.building_lines) + entity_lines.update(dataset.ambient_groups) + + for line in entity_lines.values(): + if line.get_class_id() == class_id: + affected_entities.append(line) + + else: + return patches + + upgrade_func = UPGRADE_ATTRIBUTE_FUNCS[attribute_type] + for affected_entity in affected_entities: + patches.extend(upgrade_func(converter_group, affected_entity, value, operator, team)) + + return patches diff --git a/openage/convert/processor/conversion/de2/tech/resource_modify.py b/openage/convert/processor/conversion/de2/tech/resource_modify.py new file mode 100644 index 0000000000..a70e5b1e32 --- /dev/null +++ b/openage/convert/processor/conversion/de2/tech/resource_modify.py @@ -0,0 +1,58 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying resources. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .upgrade_funcs import UPGRADE_RESOURCE_FUNCS + +if typing.TYPE_CHECKING: + from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup + from openage.convert.entity_object.conversion.aoc.genie_effect import GenieEffectObject + from openage.convert.value_object.conversion.forward_ref import ForwardRef + + +@staticmethod +def resource_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying resources. + """ + patches = [] + + effect_type = effect.get_type() + operator = None + if effect_type in (1, 11): + mode = effect["attr_b"].value + + if mode == 0: + operator = MemberOperator.ASSIGN + + else: + operator = MemberOperator.ADD + + elif effect_type in (6, 16): + operator = MemberOperator.MULTIPLY + + else: + raise TypeError(f"Effect type {effect_type} is not a valid attribute effect") + + resource_id = effect["attr_a"].value + value = effect["attr_d"].value + + if resource_id in (-1, 6, 21): + # -1 = invalid ID + # 6 = set current age (unused) + # 21 = tech count (unused) + return patches + + upgrade_func = UPGRADE_RESOURCE_FUNCS[resource_id] + patches.extend(upgrade_func(converter_group, value, operator, team)) + + return patches diff --git a/openage/convert/processor/conversion/de2/tech/upgrade_funcs.py b/openage/convert/processor/conversion/de2/tech/upgrade_funcs.py new file mode 100644 index 0000000000..8f67c59f3c --- /dev/null +++ b/openage/convert/processor/conversion/de2/tech/upgrade_funcs.py @@ -0,0 +1,153 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Mappings of DE2 upgrade IDs to their respective subprocessor functions. +""" +from ...aoc.upgrade_attribute_subprocessor import AoCUpgradeAttributeSubprocessor +from ...aoc.upgrade_resource_subprocessor import AoCUpgradeResourceSubprocessor +from ..upgrade_attribute_subprocessor import DE2UpgradeAttributeSubprocessor +from ..upgrade_resource_subprocessor import DE2UpgradeResourceSubprocessor + + +UPGRADE_ATTRIBUTE_FUNCS = { + 0: AoCUpgradeAttributeSubprocessor.hp_upgrade, + 1: AoCUpgradeAttributeSubprocessor.los_upgrade, + 2: AoCUpgradeAttributeSubprocessor.garrison_capacity_upgrade, + 3: AoCUpgradeAttributeSubprocessor.unit_size_x_upgrade, + 4: AoCUpgradeAttributeSubprocessor.unit_size_y_upgrade, + 5: AoCUpgradeAttributeSubprocessor.move_speed_upgrade, + 6: AoCUpgradeAttributeSubprocessor.rotation_speed_upgrade, + 8: AoCUpgradeAttributeSubprocessor.armor_upgrade, + 9: AoCUpgradeAttributeSubprocessor.attack_upgrade, + 10: AoCUpgradeAttributeSubprocessor.reload_time_upgrade, + 11: AoCUpgradeAttributeSubprocessor.accuracy_upgrade, + 12: AoCUpgradeAttributeSubprocessor.max_range_upgrade, + 13: AoCUpgradeAttributeSubprocessor.work_rate_upgrade, + 14: AoCUpgradeAttributeSubprocessor.carry_capacity_upgrade, + 16: AoCUpgradeAttributeSubprocessor.projectile_unit_upgrade, + 17: AoCUpgradeAttributeSubprocessor.graphics_angle_upgrade, + 18: AoCUpgradeAttributeSubprocessor.terrain_defense_upgrade, + 19: AoCUpgradeAttributeSubprocessor.ballistics_upgrade, + 20: AoCUpgradeAttributeSubprocessor.min_range_upgrade, + 21: AoCUpgradeAttributeSubprocessor.resource_storage_1_upgrade, + 22: AoCUpgradeAttributeSubprocessor.blast_radius_upgrade, + 23: AoCUpgradeAttributeSubprocessor.search_radius_upgrade, + + # TODO: These refer to different atributes in DE2 + 24: AoCUpgradeAttributeSubprocessor.imperial_tech_id_upgrade, + 26: AoCUpgradeAttributeSubprocessor.attack_warning_sound_upgrade, + 42: AoCUpgradeAttributeSubprocessor.standing_wonders_upgrade, + 43: AoCUpgradeAttributeSubprocessor.train_button_upgrade, + 46: AoCUpgradeAttributeSubprocessor.tribute_inefficiency_upgrade, + 48: AoCUpgradeAttributeSubprocessor.tc_available_upgrade, + 49: AoCUpgradeAttributeSubprocessor.gold_counter_upgrade, + 57: AoCUpgradeAttributeSubprocessor.kidnap_storage_upgrade, + + 30: DE2UpgradeAttributeSubprocessor.herdable_capacity_upgrade, + 51: DE2UpgradeAttributeSubprocessor.bfg_unknown_51_upgrade, + 59: DE2UpgradeAttributeSubprocessor.charge_attack_upgrade, + 60: DE2UpgradeAttributeSubprocessor.charge_regen_upgrade, + 61: DE2UpgradeAttributeSubprocessor.charge_event_upgrade, + 62: DE2UpgradeAttributeSubprocessor.charge_type_upgrade, + 63: AoCUpgradeAttributeSubprocessor.ignore_armor_upgrade, + 71: DE2UpgradeAttributeSubprocessor.bfg_unknown_71_upgrade, + 73: DE2UpgradeAttributeSubprocessor.bfg_unknown_73_upgrade, + 100: AoCUpgradeAttributeSubprocessor.resource_cost_upgrade, + 101: AoCUpgradeAttributeSubprocessor.creation_time_upgrade, + 102: AoCUpgradeAttributeSubprocessor.min_projectiles_upgrade, + 103: AoCUpgradeAttributeSubprocessor.cost_food_upgrade, + 104: AoCUpgradeAttributeSubprocessor.cost_wood_upgrade, + 105: AoCUpgradeAttributeSubprocessor.cost_gold_upgrade, + 106: AoCUpgradeAttributeSubprocessor.cost_stone_upgrade, + 107: AoCUpgradeAttributeSubprocessor.max_projectiles_upgrade, + 108: AoCUpgradeAttributeSubprocessor.garrison_heal_upgrade, + 109: DE2UpgradeAttributeSubprocessor.regeneration_rate_upgrade, + 110: DE2UpgradeAttributeSubprocessor.villager_pop_space_upgrade, + 111: DE2UpgradeAttributeSubprocessor.min_convert_upgrade, + 112: DE2UpgradeAttributeSubprocessor.max_convert_upgrade, + 113: DE2UpgradeAttributeSubprocessor.convert_chance_upgrade, +} + +UPGRADE_RESOURCE_FUNCS = { + 0: DE2UpgradeResourceSubprocessor.current_food_amount_upgrade, + 1: DE2UpgradeResourceSubprocessor.current_wood_amount_upgrade, + 2: DE2UpgradeResourceSubprocessor.current_stone_amount_upgrade, + 3: DE2UpgradeResourceSubprocessor.current_gold_amount_upgrade, + 4: AoCUpgradeResourceSubprocessor.starting_population_space_upgrade, + 27: AoCUpgradeResourceSubprocessor.monk_conversion_upgrade, + 28: AoCUpgradeResourceSubprocessor.building_conversion_upgrade, + 29: AoCUpgradeResourceSubprocessor.siege_conversion_upgrade, + 32: AoCUpgradeResourceSubprocessor.bonus_population_upgrade, + 35: AoCUpgradeResourceSubprocessor.faith_recharge_rate_upgrade, + 36: AoCUpgradeResourceSubprocessor.farm_food_upgrade, + 46: AoCUpgradeResourceSubprocessor.tribute_inefficiency_upgrade, + 47: AoCUpgradeResourceSubprocessor.gather_gold_efficiency_upgrade, + 50: AoCUpgradeResourceSubprocessor.reveal_ally_upgrade, + 69: DE2UpgradeResourceSubprocessor.bfg_unknown_69_upgrade, + 77: AoCUpgradeResourceSubprocessor.conversion_resistance_upgrade, + 78: AoCUpgradeResourceSubprocessor.trade_penalty_upgrade, + 79: AoCUpgradeResourceSubprocessor.gather_stone_efficiency_upgrade, + 84: AoCUpgradeResourceSubprocessor.starting_villagers_upgrade, + 85: AoCUpgradeResourceSubprocessor.chinese_tech_discount_upgrade, + 86: AoCUpgradeResourceSubprocessor.research_time_upgrade, + 88: DE2UpgradeResourceSubprocessor.fish_trap_food_amount_upgrade, + 89: AoCUpgradeResourceSubprocessor.heal_rate_upgrade, + 90: AoCUpgradeResourceSubprocessor.heal_range_upgrade, + 91: AoCUpgradeResourceSubprocessor.starting_food_upgrade, + 92: AoCUpgradeResourceSubprocessor.starting_wood_upgrade, + 93: AoCUpgradeResourceSubprocessor.starting_stone_upgrade, + 94: AoCUpgradeResourceSubprocessor.starting_gold_upgrade, + 96: AoCUpgradeResourceSubprocessor.berserk_heal_rate_upgrade, + 97: AoCUpgradeResourceSubprocessor.herding_dominance_upgrade, + 176: DE2UpgradeResourceSubprocessor.conversion_min_adjustment_upgrade, + 177: DE2UpgradeResourceSubprocessor.conversion_max_adjustment_upgrade, + 178: AoCUpgradeResourceSubprocessor.conversion_resistance_min_rounds_upgrade, + 179: AoCUpgradeResourceSubprocessor.conversion_resistance_max_rounds_upgrade, + 180: DE2UpgradeResourceSubprocessor.conversion_min_building_upgrade, + 181: DE2UpgradeResourceSubprocessor.conversion_max_building_upgrade, + 182: DE2UpgradeResourceSubprocessor.conversion_building_chance_upgrade, + 183: AoCUpgradeResourceSubprocessor.reveal_enemy_upgrade, + 189: AoCUpgradeResourceSubprocessor.gather_wood_efficiency_upgrade, + 190: AoCUpgradeResourceSubprocessor.gather_food_efficiency_upgrade, + 191: AoCUpgradeResourceSubprocessor.relic_gold_bonus_upgrade, + 192: AoCUpgradeResourceSubprocessor.heresy_upgrade, + 193: AoCUpgradeResourceSubprocessor.theocracy_upgrade, + 194: AoCUpgradeResourceSubprocessor.crenellations_upgrade, + 196: AoCUpgradeResourceSubprocessor.wonder_time_increase_upgrade, + 197: AoCUpgradeResourceSubprocessor.spies_discount_upgrade, + 208: DE2UpgradeResourceSubprocessor.feitoria_gold_upgrade, + 209: DE2UpgradeResourceSubprocessor.reveal_enemy_tcs_upgrade, + 211: DE2UpgradeResourceSubprocessor.elevation_attack_upgrade, + 212: DE2UpgradeResourceSubprocessor.cliff_attack_upgrade, + 214: DE2UpgradeResourceSubprocessor.free_kipchaks_upgrade, + 216: DE2UpgradeResourceSubprocessor.sheep_food_amount_upgrade, + 218: DE2UpgradeResourceSubprocessor.cuman_tc_upgrade, + 219: DE2UpgradeResourceSubprocessor.bfg_unknown_219_upgrade, + 220: DE2UpgradeResourceSubprocessor.relic_food_production_upgrade, + 234: DE2UpgradeResourceSubprocessor.first_crusade_upgrade, + 236: DE2UpgradeResourceSubprocessor.burgundian_vineyards_upgrade, + 237: DE2UpgradeResourceSubprocessor.folwark_collect_upgrade, + 238: DE2UpgradeResourceSubprocessor.folwark_flag_upgrade, + 239: DE2UpgradeResourceSubprocessor.folwark_mill_id_upgrade, + 241: DE2UpgradeResourceSubprocessor.stone_gold_gen_upgrade, + 242: DE2UpgradeResourceSubprocessor.workshop_food_gen_upgrade, + 243: DE2UpgradeResourceSubprocessor.workshop_wood_gen_upgrade, + 244: DE2UpgradeResourceSubprocessor.workshop_stone_gen_upgrade, + 245: DE2UpgradeResourceSubprocessor.workshop_gold_gen_upgrade, + 251: DE2UpgradeResourceSubprocessor.trade_food_bonus_upgrade, + 254: DE2UpgradeResourceSubprocessor.herdable_garrison_upgrade, + 262: DE2UpgradeResourceSubprocessor.bengali_conversion_resistance_upgrade, + 266: DE2UpgradeResourceSubprocessor.doi_paper_money_upgrade, + 267: DE2UpgradeResourceSubprocessor.forager_wood_gather_upgrade, + 268: DE2UpgradeResourceSubprocessor.resource_decay_upgrade, + 269: DE2UpgradeResourceSubprocessor.tech_reward_upgrade, + 272: DE2UpgradeResourceSubprocessor.cliff_defense_upgrade, + 273: DE2UpgradeResourceSubprocessor.elevation_defense_upgrade, + 274: DE2UpgradeResourceSubprocessor.chieftains_upgrade, + 280: DE2UpgradeResourceSubprocessor.conversion_range_upgrade, + 282: DE2UpgradeResourceSubprocessor.unknown_recharge_rate_upgrade, + 502: DE2UpgradeResourceSubprocessor.bfg_unknown_502_upgrade, + 507: DE2UpgradeResourceSubprocessor.bfg_unknown_507_upgrade, + 521: DE2UpgradeResourceSubprocessor.bfg_unknown_521_upgrade, + 551: DE2UpgradeResourceSubprocessor.bfg_unknown_551_upgrade, +} diff --git a/openage/convert/processor/conversion/de2/tech_subprocessor.py b/openage/convert/processor/conversion/de2/tech_subprocessor.py index 7bce1d1163..8f65c63351 100644 --- a/openage/convert/processor/conversion/de2/tech_subprocessor.py +++ b/openage/convert/processor/conversion/de2/tech_subprocessor.py @@ -1,6 +1,4 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-branches +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates patches for technologies. @@ -8,18 +6,15 @@ from __future__ import annotations import typing - -from .....nyan.nyan_structs import MemberOperator from ....entity_object.conversion.aoc.genie_tech import CivTeamBonus, CivBonus from ..aoc.tech_subprocessor import AoCTechSubprocessor -from ..aoc.upgrade_attribute_subprocessor import AoCUpgradeAttributeSubprocessor -from ..aoc.upgrade_resource_subprocessor import AoCUpgradeResourceSubprocessor -from .upgrade_attribute_subprocessor import DE2UpgradeAttributeSubprocessor -from .upgrade_resource_subprocessor import DE2UpgradeResourceSubprocessor + +from .tech.attribute_modify import attribute_modify_effect +from .tech.resource_modify import resource_modify_effect +from .tech.upgrade_funcs import UPGRADE_ATTRIBUTE_FUNCS, UPGRADE_RESOURCE_FUNCS if typing.TYPE_CHECKING: from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_effect import GenieEffectObject from openage.convert.value_object.conversion.forward_ref import ForwardRef @@ -28,148 +23,8 @@ class DE2TechSubprocessor: Creates raw API objects and patches for techs and civ setups in DE2. """ - upgrade_attribute_funcs = { - 0: AoCUpgradeAttributeSubprocessor.hp_upgrade, - 1: AoCUpgradeAttributeSubprocessor.los_upgrade, - 2: AoCUpgradeAttributeSubprocessor.garrison_capacity_upgrade, - 3: AoCUpgradeAttributeSubprocessor.unit_size_x_upgrade, - 4: AoCUpgradeAttributeSubprocessor.unit_size_y_upgrade, - 5: AoCUpgradeAttributeSubprocessor.move_speed_upgrade, - 6: AoCUpgradeAttributeSubprocessor.rotation_speed_upgrade, - 8: AoCUpgradeAttributeSubprocessor.armor_upgrade, - 9: AoCUpgradeAttributeSubprocessor.attack_upgrade, - 10: AoCUpgradeAttributeSubprocessor.reload_time_upgrade, - 11: AoCUpgradeAttributeSubprocessor.accuracy_upgrade, - 12: AoCUpgradeAttributeSubprocessor.max_range_upgrade, - 13: AoCUpgradeAttributeSubprocessor.work_rate_upgrade, - 14: AoCUpgradeAttributeSubprocessor.carry_capacity_upgrade, - 16: AoCUpgradeAttributeSubprocessor.projectile_unit_upgrade, - 17: AoCUpgradeAttributeSubprocessor.graphics_angle_upgrade, - 18: AoCUpgradeAttributeSubprocessor.terrain_defense_upgrade, - 19: AoCUpgradeAttributeSubprocessor.ballistics_upgrade, - 20: AoCUpgradeAttributeSubprocessor.min_range_upgrade, - 21: AoCUpgradeAttributeSubprocessor.resource_storage_1_upgrade, - 22: AoCUpgradeAttributeSubprocessor.blast_radius_upgrade, - 23: AoCUpgradeAttributeSubprocessor.search_radius_upgrade, - - # TODO: These refer to different atributes in DE2 - 24: AoCUpgradeAttributeSubprocessor.imperial_tech_id_upgrade, - 26: AoCUpgradeAttributeSubprocessor.attack_warning_sound_upgrade, - 42: AoCUpgradeAttributeSubprocessor.standing_wonders_upgrade, - 43: AoCUpgradeAttributeSubprocessor.train_button_upgrade, - 46: AoCUpgradeAttributeSubprocessor.tribute_inefficiency_upgrade, - 48: AoCUpgradeAttributeSubprocessor.tc_available_upgrade, - 49: AoCUpgradeAttributeSubprocessor.gold_counter_upgrade, - 57: AoCUpgradeAttributeSubprocessor.kidnap_storage_upgrade, - - 30: DE2UpgradeAttributeSubprocessor.herdable_capacity_upgrade, - 51: DE2UpgradeAttributeSubprocessor.bfg_unknown_51_upgrade, - 59: DE2UpgradeAttributeSubprocessor.charge_attack_upgrade, - 60: DE2UpgradeAttributeSubprocessor.charge_regen_upgrade, - 61: DE2UpgradeAttributeSubprocessor.charge_event_upgrade, - 62: DE2UpgradeAttributeSubprocessor.charge_type_upgrade, - 63: AoCUpgradeAttributeSubprocessor.ignore_armor_upgrade, - 71: DE2UpgradeAttributeSubprocessor.bfg_unknown_71_upgrade, - 73: DE2UpgradeAttributeSubprocessor.bfg_unknown_73_upgrade, - 100: AoCUpgradeAttributeSubprocessor.resource_cost_upgrade, - 101: AoCUpgradeAttributeSubprocessor.creation_time_upgrade, - 102: AoCUpgradeAttributeSubprocessor.min_projectiles_upgrade, - 103: AoCUpgradeAttributeSubprocessor.cost_food_upgrade, - 104: AoCUpgradeAttributeSubprocessor.cost_wood_upgrade, - 105: AoCUpgradeAttributeSubprocessor.cost_gold_upgrade, - 106: AoCUpgradeAttributeSubprocessor.cost_stone_upgrade, - 107: AoCUpgradeAttributeSubprocessor.max_projectiles_upgrade, - 108: AoCUpgradeAttributeSubprocessor.garrison_heal_upgrade, - 109: DE2UpgradeAttributeSubprocessor.regeneration_rate_upgrade, - 110: DE2UpgradeAttributeSubprocessor.villager_pop_space_upgrade, - 111: DE2UpgradeAttributeSubprocessor.min_convert_upgrade, - 112: DE2UpgradeAttributeSubprocessor.max_convert_upgrade, - 113: DE2UpgradeAttributeSubprocessor.convert_chance_upgrade, - } - - upgrade_resource_funcs = { - 0: DE2UpgradeResourceSubprocessor.current_food_amount_upgrade, - 1: DE2UpgradeResourceSubprocessor.current_wood_amount_upgrade, - 2: DE2UpgradeResourceSubprocessor.current_stone_amount_upgrade, - 3: DE2UpgradeResourceSubprocessor.current_gold_amount_upgrade, - 4: AoCUpgradeResourceSubprocessor.starting_population_space_upgrade, - 27: AoCUpgradeResourceSubprocessor.monk_conversion_upgrade, - 28: AoCUpgradeResourceSubprocessor.building_conversion_upgrade, - 29: AoCUpgradeResourceSubprocessor.siege_conversion_upgrade, - 32: AoCUpgradeResourceSubprocessor.bonus_population_upgrade, - 35: AoCUpgradeResourceSubprocessor.faith_recharge_rate_upgrade, - 36: AoCUpgradeResourceSubprocessor.farm_food_upgrade, - 46: AoCUpgradeResourceSubprocessor.tribute_inefficiency_upgrade, - 47: AoCUpgradeResourceSubprocessor.gather_gold_efficiency_upgrade, - 50: AoCUpgradeResourceSubprocessor.reveal_ally_upgrade, - 69: DE2UpgradeResourceSubprocessor.bfg_unknown_69_upgrade, - 77: AoCUpgradeResourceSubprocessor.conversion_resistance_upgrade, - 78: AoCUpgradeResourceSubprocessor.trade_penalty_upgrade, - 79: AoCUpgradeResourceSubprocessor.gather_stone_efficiency_upgrade, - 84: AoCUpgradeResourceSubprocessor.starting_villagers_upgrade, - 85: AoCUpgradeResourceSubprocessor.chinese_tech_discount_upgrade, - 86: AoCUpgradeResourceSubprocessor.research_time_upgrade, - 88: DE2UpgradeResourceSubprocessor.fish_trap_food_amount_upgrade, - 89: AoCUpgradeResourceSubprocessor.heal_rate_upgrade, - 90: AoCUpgradeResourceSubprocessor.heal_range_upgrade, - 91: AoCUpgradeResourceSubprocessor.starting_food_upgrade, - 92: AoCUpgradeResourceSubprocessor.starting_wood_upgrade, - 93: AoCUpgradeResourceSubprocessor.starting_stone_upgrade, - 94: AoCUpgradeResourceSubprocessor.starting_gold_upgrade, - 96: AoCUpgradeResourceSubprocessor.berserk_heal_rate_upgrade, - 97: AoCUpgradeResourceSubprocessor.herding_dominance_upgrade, - 176: DE2UpgradeResourceSubprocessor.conversion_min_adjustment_upgrade, - 177: DE2UpgradeResourceSubprocessor.conversion_max_adjustment_upgrade, - 178: AoCUpgradeResourceSubprocessor.conversion_resistance_min_rounds_upgrade, - 179: AoCUpgradeResourceSubprocessor.conversion_resistance_max_rounds_upgrade, - 180: DE2UpgradeResourceSubprocessor.conversion_min_building_upgrade, - 181: DE2UpgradeResourceSubprocessor.conversion_max_building_upgrade, - 182: DE2UpgradeResourceSubprocessor.conversion_building_chance_upgrade, - 183: AoCUpgradeResourceSubprocessor.reveal_enemy_upgrade, - 189: AoCUpgradeResourceSubprocessor.gather_wood_efficiency_upgrade, - 190: AoCUpgradeResourceSubprocessor.gather_food_efficiency_upgrade, - 191: AoCUpgradeResourceSubprocessor.relic_gold_bonus_upgrade, - 192: AoCUpgradeResourceSubprocessor.heresy_upgrade, - 193: AoCUpgradeResourceSubprocessor.theocracy_upgrade, - 194: AoCUpgradeResourceSubprocessor.crenellations_upgrade, - 196: AoCUpgradeResourceSubprocessor.wonder_time_increase_upgrade, - 197: AoCUpgradeResourceSubprocessor.spies_discount_upgrade, - 208: DE2UpgradeResourceSubprocessor.feitoria_gold_upgrade, - 209: DE2UpgradeResourceSubprocessor.reveal_enemy_tcs_upgrade, - 211: DE2UpgradeResourceSubprocessor.elevation_attack_upgrade, - 212: DE2UpgradeResourceSubprocessor.cliff_attack_upgrade, - 214: DE2UpgradeResourceSubprocessor.free_kipchaks_upgrade, - 216: DE2UpgradeResourceSubprocessor.sheep_food_amount_upgrade, - 218: DE2UpgradeResourceSubprocessor.cuman_tc_upgrade, - 219: DE2UpgradeResourceSubprocessor.bfg_unknown_219_upgrade, - 220: DE2UpgradeResourceSubprocessor.relic_food_production_upgrade, - 234: DE2UpgradeResourceSubprocessor.first_crusade_upgrade, - 236: DE2UpgradeResourceSubprocessor.burgundian_vineyards_upgrade, - 237: DE2UpgradeResourceSubprocessor.folwark_collect_upgrade, - 238: DE2UpgradeResourceSubprocessor.folwark_flag_upgrade, - 239: DE2UpgradeResourceSubprocessor.folwark_mill_id_upgrade, - 241: DE2UpgradeResourceSubprocessor.stone_gold_gen_upgrade, - 242: DE2UpgradeResourceSubprocessor.workshop_food_gen_upgrade, - 243: DE2UpgradeResourceSubprocessor.workshop_wood_gen_upgrade, - 244: DE2UpgradeResourceSubprocessor.workshop_stone_gen_upgrade, - 245: DE2UpgradeResourceSubprocessor.workshop_gold_gen_upgrade, - 251: DE2UpgradeResourceSubprocessor.trade_food_bonus_upgrade, - 254: DE2UpgradeResourceSubprocessor.herdable_garrison_upgrade, - 262: DE2UpgradeResourceSubprocessor.bengali_conversion_resistance_upgrade, - 266: DE2UpgradeResourceSubprocessor.doi_paper_money_upgrade, - 267: DE2UpgradeResourceSubprocessor.forager_wood_gather_upgrade, - 268: DE2UpgradeResourceSubprocessor.resource_decay_upgrade, - 269: DE2UpgradeResourceSubprocessor.tech_reward_upgrade, - 272: DE2UpgradeResourceSubprocessor.cliff_defense_upgrade, - 273: DE2UpgradeResourceSubprocessor.elevation_defense_upgrade, - 274: DE2UpgradeResourceSubprocessor.chieftains_upgrade, - 280: DE2UpgradeResourceSubprocessor.conversion_range_upgrade, - 282: DE2UpgradeResourceSubprocessor.unknown_recharge_rate_upgrade, - 502: DE2UpgradeResourceSubprocessor.bfg_unknown_502_upgrade, - 507: DE2UpgradeResourceSubprocessor.bfg_unknown_507_upgrade, - 521: DE2UpgradeResourceSubprocessor.bfg_unknown_521_upgrade, - 551: DE2UpgradeResourceSubprocessor.bfg_unknown_551_upgrade, - } + upgrade_attribute_funcs = UPGRADE_ATTRIBUTE_FUNCS + upgrade_resource_funcs = UPGRADE_RESOURCE_FUNCS @classmethod def get_patches(cls, converter_group: ConverterObjectGroup) -> list[ForwardRef]: @@ -241,112 +96,5 @@ def get_patches(cls, converter_group: ConverterObjectGroup) -> list[ForwardRef]: return patches - @staticmethod - def attribute_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying attributes of entities. - """ - patches = [] - dataset = converter_group.data - - effect_type = effect.get_type() - operator = None - if effect_type in (0, 10): - operator = MemberOperator.ASSIGN - - elif effect_type in (4, 14): - operator = MemberOperator.ADD - - elif effect_type in (5, 15): - operator = MemberOperator.MULTIPLY - - else: - raise TypeError(f"Effect type {effect_type} is not a valid attribute effect") - - unit_id = effect["attr_a"].value - class_id = effect["attr_b"].value - attribute_type = effect["attr_c"].value - value = effect["attr_d"].value - - if attribute_type == -1: - return patches - - affected_entities = [] - if unit_id != -1: - entity_lines = {} - entity_lines.update(dataset.unit_lines) - entity_lines.update(dataset.building_lines) - entity_lines.update(dataset.ambient_groups) - - for line in entity_lines.values(): - if line.contains_entity(unit_id): - affected_entities.append(line) - - elif attribute_type == 19: - if line.is_projectile_shooter() and line.has_projectile(unit_id): - affected_entities.append(line) - - elif class_id != -1: - entity_lines = {} - entity_lines.update(dataset.unit_lines) - entity_lines.update(dataset.building_lines) - entity_lines.update(dataset.ambient_groups) - - for line in entity_lines.values(): - if line.get_class_id() == class_id: - affected_entities.append(line) - - else: - return patches - - upgrade_func = DE2TechSubprocessor.upgrade_attribute_funcs[attribute_type] - for affected_entity in affected_entities: - patches.extend(upgrade_func(converter_group, affected_entity, value, operator, team)) - - return patches - - @staticmethod - def resource_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying resources. - """ - patches = [] - - effect_type = effect.get_type() - operator = None - if effect_type in (1, 11): - mode = effect["attr_b"].value - - if mode == 0: - operator = MemberOperator.ASSIGN - - else: - operator = MemberOperator.ADD - - elif effect_type in (6, 16): - operator = MemberOperator.MULTIPLY - - else: - raise TypeError(f"Effect type {effect_type} is not a valid attribute effect") - - resource_id = effect["attr_a"].value - value = effect["attr_d"].value - - if resource_id in (-1, 6, 21): - # -1 = invalid ID - # 6 = set current age (unused) - # 21 = tech count (unused) - return patches - - upgrade_func = DE2TechSubprocessor.upgrade_resource_funcs[resource_id] - patches.extend(upgrade_func(converter_group, value, operator, team)) - - return patches + attribute_modify_effect = staticmethod(attribute_modify_effect) + resource_modify_effect = staticmethod(resource_modify_effect) From da889e6cdcc132cf4adc8d2e7a6b0746e1c7b597 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 16:53:11 +0200 Subject: [PATCH 131/163] convert: Refactor DE2UpgradeAttributeSubprocessor into separate files. --- .../processor/conversion/de2/CMakeLists.txt | 1 + .../de2/upgrade_attribute/CMakeLists.txt | 11 + .../de2/upgrade_attribute/__init__.py | 5 + .../de2/upgrade_attribute/charge.py | 105 +++++ .../de2/upgrade_attribute/convert_chance.py | 36 ++ .../upgrade_attribute/herdable_capacity.py | 38 ++ .../de2/upgrade_attribute/max_convert.py | 36 ++ .../de2/upgrade_attribute/min_convert.py | 36 ++ .../upgrade_attribute/regeneration_rate.py | 38 ++ .../de2/upgrade_attribute/unknown.py | 82 ++++ .../upgrade_attribute/villager_pop_space.py | 38 ++ .../de2/upgrade_attribute_subprocessor.py | 409 +----------------- 12 files changed, 450 insertions(+), 385 deletions(-) create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/__init__.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/charge.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/convert_chance.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/herdable_capacity.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/max_convert.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/min_convert.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/regeneration_rate.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/unknown.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_attribute/villager_pop_space.py diff --git a/openage/convert/processor/conversion/de2/CMakeLists.txt b/openage/convert/processor/conversion/de2/CMakeLists.txt index 390c96b2ab..f26850b60a 100644 --- a/openage/convert/processor/conversion/de2/CMakeLists.txt +++ b/openage/convert/processor/conversion/de2/CMakeLists.txt @@ -17,3 +17,4 @@ add_subdirectory(main) add_subdirectory(media) add_subdirectory(nyan) add_subdirectory(tech) +add_subdirectory(upgrade_attribute) diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/CMakeLists.txt b/openage/convert/processor/conversion/de2/upgrade_attribute/CMakeLists.txt new file mode 100644 index 0000000000..4ce3bd22e2 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/CMakeLists.txt @@ -0,0 +1,11 @@ +add_py_modules( + __init__.py + charge.py + convert_chance.py + herdable_capacity.py + max_convert.py + min_convert.py + regeneration_rate.py + unknown.py + villager_pop_space.py +) diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/__init__.py b/openage/convert/processor/conversion/de2/upgrade_attribute/__init__.py new file mode 100644 index 0000000000..838ee8565c --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create upgrade patches for attributes in DE2. +""" diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/charge.py b/openage/convert/processor/conversion/de2/upgrade_attribute/charge.py new file mode 100644 index 0000000000..31128f5991 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/charge.py @@ -0,0 +1,105 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for charge effects in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def charge_attack_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the charge attack modify effect (ID: 59). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def charge_event_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the charge event modify effect (ID: 61). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def charge_regen_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the charge regen modify effect (ID: 60). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def charge_type_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the charge type modify effect (ID: 62). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/convert_chance.py b/openage/convert/processor/conversion/de2/upgrade_attribute/convert_chance.py new file mode 100644 index 0000000000..667b2e846f --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/convert_chance.py @@ -0,0 +1,36 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for convert chances in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def convert_chance_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the convert chance modify effect (ID: 113). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/herdable_capacity.py b/openage/convert/processor/conversion/de2/upgrade_attribute/herdable_capacity.py new file mode 100644 index 0000000000..5aa17efbfb --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/herdable_capacity.py @@ -0,0 +1,38 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for herdable capacity in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def herdable_capacity_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the herdable garrison capacity modify effect (ID: 30). + + TODO: Move into AK processor. + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/max_convert.py b/openage/convert/processor/conversion/de2/upgrade_attribute/max_convert.py new file mode 100644 index 0000000000..d7d71fd62d --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/max_convert.py @@ -0,0 +1,36 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the max convert interval in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def max_convert_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the max convert interval modify effect (ID: 112). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/min_convert.py b/openage/convert/processor/conversion/de2/upgrade_attribute/min_convert.py new file mode 100644 index 0000000000..44f5df3b41 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/min_convert.py @@ -0,0 +1,36 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the min convert interval in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def min_convert_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the min convert interval modify effect (ID: 111). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/regeneration_rate.py b/openage/convert/processor/conversion/de2/upgrade_attribute/regeneration_rate.py new file mode 100644 index 0000000000..0941341b41 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/regeneration_rate.py @@ -0,0 +1,38 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for regeneration rates in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def regeneration_rate_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the regeneration rate modify effect (ID: 109). + + TODO: Move into AK processor. + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/unknown.py b/openage/convert/processor/conversion/de2/upgrade_attribute/unknown.py new file mode 100644 index 0000000000..ec91d7dd70 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/unknown.py @@ -0,0 +1,82 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for unknown attributes in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def bfg_unknown_51_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown attribute effect (ID: 51). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def bfg_unknown_71_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown attribute effect (ID: 71). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def bfg_unknown_73_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown attribute effect (ID: 73). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute/villager_pop_space.py b/openage/convert/processor/conversion/de2/upgrade_attribute/villager_pop_space.py new file mode 100644 index 0000000000..63d98932f7 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_attribute/villager_pop_space.py @@ -0,0 +1,38 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for villager population space in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def villager_pop_space_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the villager pop space modify effect (ID: 110). + + TODO: Move into AK processor. + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute_subprocessor.py b/openage/convert/processor/conversion/de2/upgrade_attribute_subprocessor.py index 60a5a4f6ac..ed02351039 100644 --- a/openage/convert/processor/conversion/de2/upgrade_attribute_subprocessor.py +++ b/openage/convert/processor/conversion/de2/upgrade_attribute_subprocessor.py @@ -1,22 +1,17 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-few-public-methods -# -# TODO: Remove when all methods are implemented -# pylint: disable=unused-argument - +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates upgrade patches for attribute modification effects in DE2. """ -from __future__ import annotations -import typing - - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup - from openage.nyan.nyan_structs import MemberOperator - from openage.convert.value_object.conversion.forward_ref import ForwardRef +from .upgrade_attribute.charge import charge_attack_upgrade, charge_event_upgrade, \ + charge_regen_upgrade, charge_type_upgrade +from .upgrade_attribute.convert_chance import convert_chance_upgrade +from .upgrade_attribute.herdable_capacity import herdable_capacity_upgrade +from .upgrade_attribute.max_convert import max_convert_upgrade +from .upgrade_attribute.min_convert import min_convert_upgrade +from .upgrade_attribute.regeneration_rate import regeneration_rate_upgrade +from .upgrade_attribute.villager_pop_space import villager_pop_space_upgrade +from .upgrade_attribute.unknown import bfg_unknown_51_upgrade, bfg_unknown_71_upgrade, \ + bfg_unknown_73_upgrade class DE2UpgradeAttributeSubprocessor: @@ -24,372 +19,16 @@ class DE2UpgradeAttributeSubprocessor: Creates raw API objects for attribute upgrade effects in DE2. """ - @staticmethod - def bfg_unknown_51_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for a BfG unknown attribute effect (ID: 51). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def bfg_unknown_71_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for a BfG unknown attribute effect (ID: 71). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def bfg_unknown_73_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for a BfG unknown attribute effect (ID: 73). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def charge_attack_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the charge attack modify effect (ID: 59). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def charge_event_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the charge event modify effect (ID: 61). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def charge_regen_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the charge regen modify effect (ID: 60). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def charge_type_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the charge type modify effect (ID: 62). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def convert_chance_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the convert chance modify effect (ID: 113). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def herdable_capacity_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the herdable garrison capacity modify effect (ID: 30). - - TODO: Move into AK processor. - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def min_convert_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the min convert interval modify effect (ID: 111). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def max_convert_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the max convert interval modify effect (ID: 112). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def regeneration_rate_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the regeneration rate modify effect (ID: 109). - - TODO: Move into AK processor. - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def villager_pop_space_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the villager pop space modify effect (ID: 110). - - TODO: Move into AK processor. - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches + bfg_unknown_51_upgrade = staticmethod(bfg_unknown_51_upgrade) + bfg_unknown_71_upgrade = staticmethod(bfg_unknown_71_upgrade) + bfg_unknown_73_upgrade = staticmethod(bfg_unknown_73_upgrade) + charge_attack_upgrade = staticmethod(charge_attack_upgrade) + charge_event_upgrade = staticmethod(charge_event_upgrade) + charge_regen_upgrade = staticmethod(charge_regen_upgrade) + charge_type_upgrade = staticmethod(charge_type_upgrade) + convert_chance_upgrade = staticmethod(convert_chance_upgrade) + herdable_capacity_upgrade = staticmethod(herdable_capacity_upgrade) + max_convert_upgrade = staticmethod(max_convert_upgrade) + min_convert_upgrade = staticmethod(min_convert_upgrade) + regeneration_rate_upgrade = staticmethod(regeneration_rate_upgrade) + villager_pop_space_upgrade = staticmethod(villager_pop_space_upgrade) From 7c88ab5996788e73855cc6ba7b95d128bfd275a4 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 17:36:03 +0200 Subject: [PATCH 132/163] convert: Refactor DE2UpgradeResourceSubprocessor into separate files. --- .../processor/conversion/de2/CMakeLists.txt | 1 + .../de2/upgrade_resource/CMakeLists.txt | 30 + .../de2/upgrade_resource/__init__.py | 5 + .../bengali_conversion_resistance.py | 33 + .../upgrade_resource/burgundian_vineyards.py | 33 + .../de2/upgrade_resource/chieftains.py | 33 + .../conversion/de2/upgrade_resource/cliff.py | 54 + .../de2/upgrade_resource/convert_adjust.py | 58 + .../de2/upgrade_resource/convert_building.py | 81 ++ .../de2/upgrade_resource/convert_range.py | 33 + .../de2/upgrade_resource/cuman_tc.py | 33 + .../current_resource_amount.py | 104 ++ .../de2/upgrade_resource/doi_paper_money.py | 37 + .../de2/upgrade_resource/elevation.py | 54 + .../de2/upgrade_resource/feitoria_gold.py | 33 + .../de2/upgrade_resource/first_crusade.py | 33 + .../de2/upgrade_resource/fish_trap_amount.py | 35 + .../de2/upgrade_resource/folwark.py | 75 + .../upgrade_resource/forager_wood_gather.py | 33 + .../de2/upgrade_resource/free_kipchaks.py | 33 + .../de2/upgrade_resource/herdable_garrison.py | 33 + .../upgrade_resource/relic_food_production.py | 33 + .../de2/upgrade_resource/resource_decay.py | 33 + .../de2/upgrade_resource/reveal_enemy_tcs.py | 35 + .../de2/upgrade_resource/sheep_food_amount.py | 33 + .../de2/upgrade_resource/stone_gold_gen.py | 33 + .../de2/upgrade_resource/tech_reward.py | 33 + .../de2/upgrade_resource/trade_food_bonus.py | 33 + .../de2/upgrade_resource/unknown.py | 159 ++ .../de2/upgrade_resource/workshop_gen.py | 96 ++ .../de2/upgrade_resource_subprocessor.py | 1279 ++--------------- 31 files changed, 1440 insertions(+), 1191 deletions(-) create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/__init__.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/bengali_conversion_resistance.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/burgundian_vineyards.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/chieftains.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/cliff.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/convert_adjust.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/convert_building.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/convert_range.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/cuman_tc.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/current_resource_amount.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/doi_paper_money.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/elevation.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/feitoria_gold.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/first_crusade.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/fish_trap_amount.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/folwark.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/forager_wood_gather.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/free_kipchaks.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/herdable_garrison.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/relic_food_production.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/resource_decay.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/reveal_enemy_tcs.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/sheep_food_amount.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/stone_gold_gen.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/tech_reward.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/trade_food_bonus.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/unknown.py create mode 100644 openage/convert/processor/conversion/de2/upgrade_resource/workshop_gen.py diff --git a/openage/convert/processor/conversion/de2/CMakeLists.txt b/openage/convert/processor/conversion/de2/CMakeLists.txt index f26850b60a..9e35a798af 100644 --- a/openage/convert/processor/conversion/de2/CMakeLists.txt +++ b/openage/convert/processor/conversion/de2/CMakeLists.txt @@ -18,3 +18,4 @@ add_subdirectory(media) add_subdirectory(nyan) add_subdirectory(tech) add_subdirectory(upgrade_attribute) +add_subdirectory(upgrade_resource) diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/CMakeLists.txt b/openage/convert/processor/conversion/de2/upgrade_resource/CMakeLists.txt new file mode 100644 index 0000000000..071e11d887 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/CMakeLists.txt @@ -0,0 +1,30 @@ +add_py_modules( + __init__.py + bengali_conversion_resistance.py + burgundian_vineyards.py + chieftains.py + cliff.py + convert_adjust.py + convert_building.py + convert_range.py + cuman_tc.py + current_resource_amount.py + doi_paper_money.py + elevation.py + feitoria_gold.py + first_crusade.py + fish_trap_amount.py + folwark.py + forager_wood_gather.py + free_kipchaks.py + herdable_garrison.py + relic_food_production.py + resource_decay.py + reveal_enemy_tcs.py + sheep_food_amount.py + stone_gold_gen.py + tech_reward.py + trade_food_bonus.py + unknown.py + workshop_gen.py +) diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/__init__.py b/openage/convert/processor/conversion/de2/upgrade_resource/__init__.py new file mode 100644 index 0000000000..8d2cfa95d9 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create upgrade patches for civ resources in DE2. +""" diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/bengali_conversion_resistance.py b/openage/convert/processor/conversion/de2/upgrade_resource/bengali_conversion_resistance.py new file mode 100644 index 0000000000..f7d1178164 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/bengali_conversion_resistance.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the Bengali conversion resistance in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def bengali_conversion_resistance_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the bengali conversion resistance effect (ID: 262). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/burgundian_vineyards.py b/openage/convert/processor/conversion/de2/upgrade_resource/burgundian_vineyards.py new file mode 100644 index 0000000000..ca90f4eb6f --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/burgundian_vineyards.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the Burgundian vineyards effect in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def burgundian_vineyards_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the burgundian vineyards effect (ID: 236). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/chieftains.py b/openage/convert/processor/conversion/de2/upgrade_resource/chieftains.py new file mode 100644 index 0000000000..ac238da9c2 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/chieftains.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the Chieftains effect in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def chieftains_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for activating looting (ID: 274). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/cliff.py b/openage/convert/processor/conversion/de2/upgrade_resource/cliff.py new file mode 100644 index 0000000000..b5109515d3 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/cliff.py @@ -0,0 +1,54 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the cliff damage effects in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def cliff_attack_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the cliff attack multiplier effect (ID: 212). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def cliff_defense_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the cliff defense multiplier effect (ID: 272). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/convert_adjust.py b/openage/convert/processor/conversion/de2/upgrade_resource/convert_adjust.py new file mode 100644 index 0000000000..e08a1f9357 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/convert_adjust.py @@ -0,0 +1,58 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the conversion effect adjustments in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def convert_min_adjust_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion min adjustment modify effect (ID: 176). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def convert_max_adjust_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion max adjustment modify effect (ID: 177). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/convert_building.py b/openage/convert/processor/conversion/de2/upgrade_resource/convert_building.py new file mode 100644 index 0000000000..011422a666 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/convert_building.py @@ -0,0 +1,81 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the conversion of buildings in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def convert_min_building_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion min building modify effect (ID: 180). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def convert_max_building_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion max building modify effect (ID: 181). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def convert_building_chance_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion building chance modify effect (ID: 182). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/convert_range.py b/openage/convert/processor/conversion/de2/upgrade_resource/convert_range.py new file mode 100644 index 0000000000..f82e881cf2 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/convert_range.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for conversion range in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def convert_range_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion range modifer (ID: 280). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/cuman_tc.py b/openage/convert/processor/conversion/de2/upgrade_resource/cuman_tc.py new file mode 100644 index 0000000000..4e88144e45 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/cuman_tc.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for Cuman TC effect in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def cuman_tc_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the cuman TC modify effect (ID: 218). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/current_resource_amount.py b/openage/convert/processor/conversion/de2/upgrade_resource/current_resource_amount.py new file mode 100644 index 0000000000..db8f07bfa6 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/current_resource_amount.py @@ -0,0 +1,104 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for current resource amounts in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def current_food_amount_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the current food amount modify effect (ID: 0). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def current_wood_amount_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the current wood amount modify effect (ID: 1). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def current_stone_amount_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the current stone amount modify effect (ID: 2). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def current_gold_amount_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the current gold amount modify effect (ID: 3). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/doi_paper_money.py b/openage/convert/processor/conversion/de2/upgrade_resource/doi_paper_money.py new file mode 100644 index 0000000000..ca78a5f92c --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/doi_paper_money.py @@ -0,0 +1,37 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the paper money tech effect in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def doi_paper_money_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the Paper Money effect in Dynasties of India (ID: 266). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/elevation.py b/openage/convert/processor/conversion/de2/upgrade_resource/elevation.py new file mode 100644 index 0000000000..445534412d --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/elevation.py @@ -0,0 +1,54 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for elevation damage in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def elevation_attack_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the elevation attack multiplier effect (ID: 211). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def elevation_defense_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the elevation defense multiplier effect (ID: 273). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/feitoria_gold.py b/openage/convert/processor/conversion/de2/upgrade_resource/feitoria_gold.py new file mode 100644 index 0000000000..f05e140cc8 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/feitoria_gold.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for feitoria gold generation in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def feitoria_gold_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the feitoria gold productivity effect (ID: 208). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement all resources + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/first_crusade.py b/openage/convert/processor/conversion/de2/upgrade_resource/first_crusade.py new file mode 100644 index 0000000000..e5b1a7be5e --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/first_crusade.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the first crusade effect in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def first_crusade_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the first crusade effect (ID: 234). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/fish_trap_amount.py b/openage/convert/processor/conversion/de2/upgrade_resource/fish_trap_amount.py new file mode 100644 index 0000000000..08afa28176 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/fish_trap_amount.py @@ -0,0 +1,35 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for fish trap resource amounts in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def fish_trap_food_amount_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the fish trap food amount modify effect (ID: 88). + + TODO: Move into AoC processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/folwark.py b/openage/convert/processor/conversion/de2/upgrade_resource/folwark.py new file mode 100644 index 0000000000..fec684e3bc --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/folwark.py @@ -0,0 +1,75 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for folwark effects in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def folwark_collect_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the folwark collect amount modify effect (ID: 237). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def folwark_flag_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the folwark flag effect (ID: 238). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def folwark_mill_id_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the folwark mill ID set effect (ID: 239). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/forager_wood_gather.py b/openage/convert/processor/conversion/de2/upgrade_resource/forager_wood_gather.py new file mode 100644 index 0000000000..758bdb5b69 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/forager_wood_gather.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for forager wood gather effects in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def forager_wood_gather_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the portugese forage wood gather effect (ID: 267). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/free_kipchaks.py b/openage/convert/processor/conversion/de2/upgrade_resource/free_kipchaks.py new file mode 100644 index 0000000000..21f39e5a57 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/free_kipchaks.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the free kipchaks effect in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def free_kipchaks_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the current gold amount modify effect (ID: 214). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/herdable_garrison.py b/openage/convert/processor/conversion/de2/upgrade_resource/herdable_garrison.py new file mode 100644 index 0000000000..a3af2acecb --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/herdable_garrison.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for herdable garrisons in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def herdable_garrison_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the herdable garrison effect (ID: 254). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/relic_food_production.py b/openage/convert/processor/conversion/de2/upgrade_resource/relic_food_production.py new file mode 100644 index 0000000000..d122c73f03 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/relic_food_production.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for relic food production in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def relic_food_production_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the relic food production effect (ID: 220). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/resource_decay.py b/openage/convert/processor/conversion/de2/upgrade_resource/resource_decay.py new file mode 100644 index 0000000000..60c371c8b5 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/resource_decay.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for resource decay in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def resource_decay_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the resource decay modifier effect (ID: 268). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/reveal_enemy_tcs.py b/openage/convert/processor/conversion/de2/upgrade_resource/reveal_enemy_tcs.py new file mode 100644 index 0000000000..8febd5c493 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/reveal_enemy_tcs.py @@ -0,0 +1,35 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for enemy TC reveals in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def reveal_enemy_tcs_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the reveal enemy TCs effect (ID: 209). + + TODO: Move into Rajas processor + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/sheep_food_amount.py b/openage/convert/processor/conversion/de2/upgrade_resource/sheep_food_amount.py new file mode 100644 index 0000000000..0903177ab0 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/sheep_food_amount.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for sheep food amounts in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def sheep_food_amount_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the sheep food amount modify effect (ID: 216). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/stone_gold_gen.py b/openage/convert/processor/conversion/de2/upgrade_resource/stone_gold_gen.py new file mode 100644 index 0000000000..95f5eef775 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/stone_gold_gen.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for stone+gold generation in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def stone_gold_gen_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the polish stone gold generation effect (ID: 241). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/tech_reward.py b/openage/convert/processor/conversion/de2/upgrade_resource/tech_reward.py new file mode 100644 index 0000000000..dade57d345 --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/tech_reward.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for tech rewards in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def tech_reward_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the spanish tech reward effect (ID: 269). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/trade_food_bonus.py b/openage/convert/processor/conversion/de2/upgrade_resource/trade_food_bonus.py new file mode 100644 index 0000000000..94c64f2cff --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/trade_food_bonus.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for trade food bonuses in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def trade_food_bonus_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the trade food bonus effect (ID: 251). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/unknown.py b/openage/convert/processor/conversion/de2/upgrade_resource/unknown.py new file mode 100644 index 0000000000..8466bea22c --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/unknown.py @@ -0,0 +1,159 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for unknown civ resources in DE2. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def bfg_unknown_69_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 69). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def bfg_unknown_219_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 219). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def bfg_unknown_502_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 502). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def bfg_unknown_507_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 507). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def bfg_unknown_521_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 521). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def bfg_unknown_551_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 551). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def unknown_recharge_rate_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the unknown recharge rate bonus effect (ID: 282). + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource/workshop_gen.py b/openage/convert/processor/conversion/de2/upgrade_resource/workshop_gen.py new file mode 100644 index 0000000000..0efadab70a --- /dev/null +++ b/openage/convert/processor/conversion/de2/upgrade_resource/workshop_gen.py @@ -0,0 +1,96 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for workshop resource generation in DE2. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......nyan.nyan_structs import MemberOperator + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + + +def workshop_food_gen_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the workshop food generation effect (ID: 242). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def workshop_wood_gen_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the workshop wood generation effect (ID: 243). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def workshop_stone_gen_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the workshop stone generation effect (ID: 244). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def workshop_gold_gen_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the workshop gold generation effect (ID: 245). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/de2/upgrade_resource_subprocessor.py b/openage/convert/processor/conversion/de2/upgrade_resource_subprocessor.py index 9ccd9a68f2..5a4b046d74 100644 --- a/openage/convert/processor/conversion/de2/upgrade_resource_subprocessor.py +++ b/openage/convert/processor/conversion/de2/upgrade_resource_subprocessor.py @@ -1,21 +1,48 @@ -# Copyright 2020-2024 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 -# -# TODO: Remove when all methods are implemented -# pylint: disable=unused-argument +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates upgrade patches for resource modification effects in DE2. """ -from __future__ import annotations -import typing - - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.nyan.nyan_structs import MemberOperator - from openage.convert.value_object.conversion.forward_ref import ForwardRef +from .upgrade_resource.bengali_conversion_resistance import bengali_conversion_resistance_upgrade +from .upgrade_resource.burgundian_vineyards import burgundian_vineyards_upgrade +from .upgrade_resource.chieftains import chieftains_upgrade +from .upgrade_resource.cliff import cliff_attack_upgrade, cliff_defense_upgrade +from .upgrade_resource.convert_adjust import convert_min_adjust_upgrade, \ + convert_max_adjust_upgrade +from .upgrade_resource.convert_building import convert_min_building_upgrade, \ + convert_max_building_upgrade, convert_building_chance_upgrade +from .upgrade_resource.convert_range import convert_range_upgrade +from .upgrade_resource.cuman_tc import cuman_tc_upgrade +from .upgrade_resource.current_resource_amount import current_food_amount_upgrade, \ + current_wood_amount_upgrade, current_stone_amount_upgrade, \ + current_gold_amount_upgrade +from .upgrade_resource.doi_paper_money import doi_paper_money_upgrade +from .upgrade_resource.elevation import elevation_attack_upgrade, \ + elevation_defense_upgrade +from .upgrade_resource.feitoria_gold import feitoria_gold_upgrade +from .upgrade_resource.first_crusade import first_crusade_upgrade +from .upgrade_resource.fish_trap_amount import fish_trap_food_amount_upgrade +from .upgrade_resource.folwark import folwark_collect_upgrade, \ + folwark_flag_upgrade, folwark_mill_id_upgrade +from .upgrade_resource.forager_wood_gather import forager_wood_gather_upgrade +from .upgrade_resource.free_kipchaks import free_kipchaks_upgrade +from .upgrade_resource.herdable_garrison import herdable_garrison_upgrade +from .upgrade_resource.relic_food_production import relic_food_production_upgrade +from .upgrade_resource.resource_decay import resource_decay_upgrade +from .upgrade_resource.reveal_enemy_tcs import reveal_enemy_tcs_upgrade +from .upgrade_resource.sheep_food_amount import sheep_food_amount_upgrade +from .upgrade_resource.stone_gold_gen import stone_gold_gen_upgrade +from .upgrade_resource.tech_reward import tech_reward_upgrade +from .upgrade_resource.trade_food_bonus import trade_food_bonus_upgrade +from .upgrade_resource.workshop_gen import workshop_food_gen_upgrade, \ + workshop_wood_gen_upgrade, workshop_stone_gen_upgrade, \ + workshop_gold_gen_upgrade + + +from .upgrade_resource.unknown import bfg_unknown_69_upgrade, \ + bfg_unknown_219_upgrade, bfg_unknown_502_upgrade, \ + bfg_unknown_507_upgrade, bfg_unknown_521_upgrade, \ + bfg_unknown_551_upgrade, unknown_recharge_rate_upgrade class DE2UpgradeResourceSubprocessor: @@ -23,1180 +50,50 @@ class DE2UpgradeResourceSubprocessor: Creates raw API objects for resource upgrade effects in DE2. """ - @staticmethod - def bengali_conversion_resistance_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the bengali conversion resistance effect (ID: 262). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def bfg_unknown_69_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for a BfG unknown resource effect (ID: 69). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def bfg_unknown_219_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for a BfG unknown resource effect (ID: 219). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def bfg_unknown_502_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for a BfG unknown resource effect (ID: 502). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def bfg_unknown_507_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for a BfG unknown resource effect (ID: 507). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def bfg_unknown_521_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for a BfG unknown resource effect (ID: 521). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def bfg_unknown_551_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for a BfG unknown resource effect (ID: 551). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def burgundian_vineyards_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the burgundian vineyards effect (ID: 236). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def chieftains_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for activating looting (ID: 274). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def cliff_attack_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the cliff attack multiplier effect (ID: 212). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def cliff_defense_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the cliff defense multiplier effect (ID: 272). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_min_adjustment_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion min adjustment modify effect (ID: 176). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_max_adjustment_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion max adjustment modify effect (ID: 177). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_min_building_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion min building modify effect (ID: 180). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_max_building_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion max building modify effect (ID: 181). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_building_chance_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion building chance modify effect (ID: 182). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_range_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion range modifer (ID: 280). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def cuman_tc_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the cuman TC modify effect (ID: 218). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def current_food_amount_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the current food amount modify effect (ID: 0). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def current_wood_amount_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the current wood amount modify effect (ID: 1). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def current_stone_amount_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the current stone amount modify effect (ID: 2). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def current_gold_amount_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the current gold amount modify effect (ID: 3). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def doi_paper_money_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the Paper Money effect in Dynasties of India (ID: 266). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def elevation_attack_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the elevation attack multiplier effect (ID: 211). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def elevation_defense_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the elevation defense multiplier effect (ID: 273). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def feitoria_gold_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the feitoria gold productivity effect (ID: 208). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement all resources - - return patches - - @staticmethod - def first_crusade_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the first crusade effect (ID: 234). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def fish_trap_food_amount_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the fish trap food amount modify effect (ID: 88). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def folwark_collect_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the folwark collect amount modify effect (ID: 237). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def folwark_flag_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the folwark flag effect (ID: 238). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def folwark_mill_id_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the folwark mill ID set effect (ID: 239). - - TODO: Move into AoC processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def forager_wood_gather_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the portugese forage wood gather effect (ID: 267). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def free_kipchaks_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the current gold amount modify effect (ID: 214). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def herdable_garrison_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the herdable garrison effect (ID: 254). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def relic_food_production_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the relic food production effect (ID: 220). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def resource_decay_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the resource decay modifier effect (ID: 268). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def reveal_enemy_tcs_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the reveal enemy TCs effect (ID: 209). - - TODO: Move into Rajas processor - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def sheep_food_amount_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the sheep food amount modify effect (ID: 216). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def stone_gold_gen_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the polish stone gold generation effect (ID: 241). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def tech_reward_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the spanish tech reward effect (ID: 269). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def trade_food_bonus_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the trade food bonus effect (ID: 251). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def unknown_recharge_rate_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the unknown recharge rate bonus effect (ID: 282). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def workshop_food_gen_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the workshop food generation effect (ID: 242). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def workshop_wood_gen_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the workshop wood generation effect (ID: 243). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def workshop_stone_gen_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the workshop stone generation effect (ID: 244). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def workshop_gold_gen_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the workshop gold generation effect (ID: 245). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: MemberOperator - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches + bengali_conversion_resistance_upgrade = staticmethod( + bengali_conversion_resistance_upgrade) + bfg_unknown_69_upgrade = staticmethod(bfg_unknown_69_upgrade) + bfg_unknown_219_upgrade = staticmethod(bfg_unknown_219_upgrade) + bfg_unknown_502_upgrade = staticmethod(bfg_unknown_502_upgrade) + bfg_unknown_507_upgrade = staticmethod(bfg_unknown_507_upgrade) + bfg_unknown_521_upgrade = staticmethod(bfg_unknown_521_upgrade) + bfg_unknown_551_upgrade = staticmethod(bfg_unknown_551_upgrade) + unknown_recharge_rate_upgrade = staticmethod(unknown_recharge_rate_upgrade) + burgundian_vineyards_upgrade = staticmethod(burgundian_vineyards_upgrade) + chieftains_upgrade = staticmethod(chieftains_upgrade) + cliff_attack_upgrade = staticmethod(cliff_attack_upgrade) + cliff_defense_upgrade = staticmethod(cliff_defense_upgrade) + conversion_min_adjustment_upgrade = staticmethod(convert_min_adjust_upgrade) + conversion_max_adjustment_upgrade = staticmethod(convert_max_adjust_upgrade) + conversion_min_building_upgrade = staticmethod(convert_min_building_upgrade) + conversion_max_building_upgrade = staticmethod(convert_max_building_upgrade) + conversion_building_chance_upgrade = staticmethod(convert_building_chance_upgrade) + conversion_range_upgrade = staticmethod(convert_range_upgrade) + cuman_tc_upgrade = staticmethod(cuman_tc_upgrade) + current_food_amount_upgrade = staticmethod(current_food_amount_upgrade) + current_wood_amount_upgrade = staticmethod(current_wood_amount_upgrade) + current_stone_amount_upgrade = staticmethod(current_stone_amount_upgrade) + current_gold_amount_upgrade = staticmethod(current_gold_amount_upgrade) + doi_paper_money_upgrade = staticmethod(doi_paper_money_upgrade) + elevation_attack_upgrade = staticmethod(elevation_attack_upgrade) + elevation_defense_upgrade = staticmethod(elevation_defense_upgrade) + feitoria_gold_upgrade = staticmethod(feitoria_gold_upgrade) + first_crusade_upgrade = staticmethod(first_crusade_upgrade) + fish_trap_food_amount_upgrade = staticmethod(fish_trap_food_amount_upgrade) + folwark_collect_upgrade = staticmethod(folwark_collect_upgrade) + folwark_flag_upgrade = staticmethod(folwark_flag_upgrade) + folwark_mill_id_upgrade = staticmethod(folwark_mill_id_upgrade) + forager_wood_gather_upgrade = staticmethod(forager_wood_gather_upgrade) + free_kipchaks_upgrade = staticmethod(free_kipchaks_upgrade) + herdable_garrison_upgrade = staticmethod(herdable_garrison_upgrade) + relic_food_production_upgrade = staticmethod(relic_food_production_upgrade) + resource_decay_upgrade = staticmethod(resource_decay_upgrade) + reveal_enemy_tcs_upgrade = staticmethod(reveal_enemy_tcs_upgrade) + sheep_food_amount_upgrade = staticmethod(sheep_food_amount_upgrade) + stone_gold_gen_upgrade = staticmethod(stone_gold_gen_upgrade) + tech_reward_upgrade = staticmethod(tech_reward_upgrade) + trade_food_bonus_upgrade = staticmethod(trade_food_bonus_upgrade) + workshop_food_gen_upgrade = staticmethod(workshop_food_gen_upgrade) + workshop_wood_gen_upgrade = staticmethod(workshop_wood_gen_upgrade) + workshop_stone_gen_upgrade = staticmethod(workshop_stone_gen_upgrade) + workshop_gold_gen_upgrade = staticmethod(workshop_gold_gen_upgrade) From cd1322701e470578de019638435abda4f0773d87 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 9 Jun 2025 17:42:41 +0200 Subject: [PATCH 133/163] convert: Refactor HD processor classes into separate files. --- .../processor/conversion/hd/CMakeLists.txt | 2 + .../conversion/hd/media/CMakeLists.txt | 5 + .../processor/conversion/hd/media/__init__.py | 5 + .../processor/conversion/hd/media/graphics.py | 152 ++++++++++++++++ .../processor/conversion/hd/media/sound.py | 34 ++++ .../conversion/hd/media_subprocessor.py | 172 +----------------- 6 files changed, 205 insertions(+), 165 deletions(-) create mode 100644 openage/convert/processor/conversion/hd/media/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/hd/media/__init__.py create mode 100644 openage/convert/processor/conversion/hd/media/graphics.py create mode 100644 openage/convert/processor/conversion/hd/media/sound.py diff --git a/openage/convert/processor/conversion/hd/CMakeLists.txt b/openage/convert/processor/conversion/hd/CMakeLists.txt index dcacbb845c..3a91b0809f 100644 --- a/openage/convert/processor/conversion/hd/CMakeLists.txt +++ b/openage/convert/processor/conversion/hd/CMakeLists.txt @@ -4,3 +4,5 @@ add_py_modules( modpack_subprocessor.py processor.py ) + +add_subdirectory(media) diff --git a/openage/convert/processor/conversion/hd/media/CMakeLists.txt b/openage/convert/processor/conversion/hd/media/CMakeLists.txt new file mode 100644 index 0000000000..74a1ead506 --- /dev/null +++ b/openage/convert/processor/conversion/hd/media/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py + graphics.py + sound.py +) diff --git a/openage/convert/processor/conversion/hd/media/__init__.py b/openage/convert/processor/conversion/hd/media/__init__.py new file mode 100644 index 0000000000..5e5a7f028f --- /dev/null +++ b/openage/convert/processor/conversion/hd/media/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create media export requests for media files in the HD data. +""" diff --git a/openage/convert/processor/conversion/hd/media/graphics.py b/openage/convert/processor/conversion/hd/media/graphics.py new file mode 100644 index 0000000000..1243b6328a --- /dev/null +++ b/openage/convert/processor/conversion/hd/media/graphics.py @@ -0,0 +1,152 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create export requests for graphics files. +""" +from __future__ import annotations +import typing + +from .....entity_object.export.formats.sprite_metadata import LayerMode as SpriteLayerMode +from .....entity_object.export.formats.terrain_metadata import LayerMode as TerrainLayerMode +from .....entity_object.export.media_export_request import MediaExportRequest +from .....entity_object.export.metadata_export import SpriteMetadataExport +from .....entity_object.export.metadata_export import TextureMetadataExport +from .....entity_object.export.metadata_export import TerrainMetadataExport +from .....value_object.read.media_types import MediaType + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: + """ + Create export requests for graphics referenced by CombinedSprite objects. + """ + combined_sprites = full_data_set.combined_sprites.values() + handled_graphic_ids = set() + + for sprite in combined_sprites: + ref_graphics = sprite.get_graphics() + graphic_targetdirs = sprite.resolve_graphics_location() + + metadata_filename = f"{sprite.get_filename()}.{'sprite'}" + sprite_meta_export = SpriteMetadataExport(sprite.resolve_sprite_location(), + metadata_filename) + full_data_set.metadata_exports.append(sprite_meta_export) + + for graphic in ref_graphics: + graphic_id = graphic.get_id() + if graphic_id in handled_graphic_ids: + continue + + targetdir = graphic_targetdirs[graphic_id] + source_filename = f"{str(graphic['slp_id'].value)}.slp" + target_filename = f"{sprite.get_filename()}_{str(graphic['slp_id'].value)}.png" + + export_request = MediaExportRequest(MediaType.GRAPHICS, + targetdir, + source_filename, + target_filename) + full_data_set.graphics_exports.update({graphic_id: export_request}) + + # Texture metadata file definiton + # Same file stem as the image file and same targetdir + texture_meta_filename = f"{target_filename[:-4]}.texture" + texture_meta_export = TextureMetadataExport(targetdir, + texture_meta_filename) + full_data_set.metadata_exports.append(texture_meta_export) + + # Add texture image filename to texture metadata + texture_meta_export.add_imagefile(target_filename) + + # Add metadata from graphics to animation metadata + sequence_type = graphic["sequence_type"].value + if sequence_type == 0x00: + layer_mode = SpriteLayerMode.OFF + + elif sequence_type & 0x08: + layer_mode = SpriteLayerMode.ONCE + + else: + layer_mode = SpriteLayerMode.LOOP + + layer_pos = graphic["layer"].value + frame_rate = round(graphic["frame_rate"].value, ndigits=6) + if frame_rate < 0.000001: + frame_rate = None + + replay_delay = round(graphic["replay_delay"].value, ndigits=6) + if replay_delay < 0.000001: + replay_delay = None + + frame_count = graphic["frame_count"].value + angle_count = graphic["angle_count"].value + mirror_mode = graphic["mirroring_mode"].value + sprite_meta_export.add_graphics_metadata(target_filename, + texture_meta_filename, + layer_mode, + layer_pos, + frame_rate, + replay_delay, + frame_count, + angle_count, + mirror_mode) + + # Notify metadata export about SLP metadata when the file is exported + export_request.add_observer(texture_meta_export) + export_request.add_observer(sprite_meta_export) + + handled_graphic_ids.add(graphic_id) + + combined_terrains = full_data_set.combined_terrains.values() + for texture in combined_terrains: + slp_id = texture.get_terrain()["slp_id"].value + srcfile_prefix = texture.get_terrain()["filename"].value + + targetdir = texture.resolve_graphics_location() + source_filename = f"{str(srcfile_prefix)}_00_color.png" + target_filename = f"{texture.get_filename()}.png" + + export_request = MediaExportRequest(MediaType.TERRAIN, + targetdir, + source_filename, + target_filename) + full_data_set.graphics_exports.update({slp_id: export_request}) + + texture_meta_filename = f"{texture.get_filename()}.texture" + texture_meta_export = TextureMetadataExport(targetdir, + texture_meta_filename) + full_data_set.metadata_exports.append(texture_meta_export) + + # Add texture image filename to texture metadata + texture_meta_export.add_imagefile(target_filename) + texture_meta_export.update( + None, + { + f"{target_filename}": { + "size": (512, 512), + "subtex_metadata": [ + { + "x": 0, + "y": 0, + "w": 512, + "h": 512, + "cx": 0, + "cy": 0, + } + ] + }} + ) + + terrain_meta_filename = f"{texture.get_filename()}.terrain" + terrain_meta_export = TerrainMetadataExport(targetdir, + terrain_meta_filename) + full_data_set.metadata_exports.append(terrain_meta_export) + + terrain_meta_export.add_graphics_metadata(target_filename, + texture_meta_filename, + TerrainLayerMode.OFF, + 0, + 0.0, + 0.0, + 1) diff --git a/openage/convert/processor/conversion/hd/media/sound.py b/openage/convert/processor/conversion/hd/media/sound.py new file mode 100644 index 0000000000..04e681819a --- /dev/null +++ b/openage/convert/processor/conversion/hd/media/sound.py @@ -0,0 +1,34 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create export requests for sound files. +""" +from __future__ import annotations +import typing + +from .....entity_object.export.media_export_request import MediaExportRequest +from .....value_object.read.media_types import MediaType + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_sound_requests(full_data_set: GenieObjectContainer) -> None: + """ + Create export requests for sounds referenced by CombinedSound objects. + """ + combined_sounds = full_data_set.combined_sounds.values() + + for sound in combined_sounds: + sound_id = sound.get_file_id() + + targetdir = sound.resolve_sound_location() + source_filename = f"{str(sound_id)}.wav" + target_filename = f"{sound.get_filename()}.opus" + + export_request = MediaExportRequest(MediaType.SOUNDS, + targetdir, + source_filename, + target_filename) + + full_data_set.sound_exports.update({sound_id: export_request}) diff --git a/openage/convert/processor/conversion/hd/media_subprocessor.py b/openage/convert/processor/conversion/hd/media_subprocessor.py index 62dc1dd719..f3cff73ca7 100644 --- a/openage/convert/processor/conversion/hd/media_subprocessor.py +++ b/openage/convert/processor/conversion/hd/media_subprocessor.py @@ -9,17 +9,11 @@ from __future__ import annotations import typing -from ....entity_object.export.formats.sprite_metadata import LayerMode as SpriteLayerMode -from ....entity_object.export.formats.terrain_metadata import LayerMode as TerrainLayerMode -from ....entity_object.export.media_export_request import MediaExportRequest -from ....entity_object.export.metadata_export import SpriteMetadataExport -from ....entity_object.export.metadata_export import TextureMetadataExport -from ....entity_object.export.metadata_export import TerrainMetadataExport -from ....value_object.read.media_types import MediaType +from .media.graphics import create_graphics_requests +from .media.sound import create_sound_requests if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_object_container\ - import GenieObjectContainer + from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer class HDMediaSubprocessor: @@ -32,160 +26,8 @@ def convert(cls, full_data_set: GenieObjectContainer) -> None: """ Create all export requests for the dataset. """ - cls.create_graphics_requests(full_data_set) - cls.create_sound_requests(full_data_set) + create_graphics_requests(full_data_set) + create_sound_requests(full_data_set) - @staticmethod - def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: - """ - Create export requests for graphics referenced by CombinedSprite objects. - """ - combined_sprites = full_data_set.combined_sprites.values() - handled_graphic_ids = set() - - for sprite in combined_sprites: - ref_graphics = sprite.get_graphics() - graphic_targetdirs = sprite.resolve_graphics_location() - - metadata_filename = f"{sprite.get_filename()}.{'sprite'}" - sprite_meta_export = SpriteMetadataExport(sprite.resolve_sprite_location(), - metadata_filename) - full_data_set.metadata_exports.append(sprite_meta_export) - - for graphic in ref_graphics: - graphic_id = graphic.get_id() - if graphic_id in handled_graphic_ids: - continue - - targetdir = graphic_targetdirs[graphic_id] - source_filename = f"{str(graphic['slp_id'].value)}.slp" - target_filename = f"{sprite.get_filename()}_{str(graphic['slp_id'].value)}.png" - - export_request = MediaExportRequest(MediaType.GRAPHICS, - targetdir, - source_filename, - target_filename) - full_data_set.graphics_exports.update({graphic_id: export_request}) - - # Texture metadata file definiton - # Same file stem as the image file and same targetdir - texture_meta_filename = f"{target_filename[:-4]}.texture" - texture_meta_export = TextureMetadataExport(targetdir, - texture_meta_filename) - full_data_set.metadata_exports.append(texture_meta_export) - - # Add texture image filename to texture metadata - texture_meta_export.add_imagefile(target_filename) - - # Add metadata from graphics to animation metadata - sequence_type = graphic["sequence_type"].value - if sequence_type == 0x00: - layer_mode = SpriteLayerMode.OFF - - elif sequence_type & 0x08: - layer_mode = SpriteLayerMode.ONCE - - else: - layer_mode = SpriteLayerMode.LOOP - - layer_pos = graphic["layer"].value - frame_rate = round(graphic["frame_rate"].value, ndigits=6) - if frame_rate < 0.000001: - frame_rate = None - - replay_delay = round(graphic["replay_delay"].value, ndigits=6) - if replay_delay < 0.000001: - replay_delay = None - - frame_count = graphic["frame_count"].value - angle_count = graphic["angle_count"].value - mirror_mode = graphic["mirroring_mode"].value - sprite_meta_export.add_graphics_metadata(target_filename, - texture_meta_filename, - layer_mode, - layer_pos, - frame_rate, - replay_delay, - frame_count, - angle_count, - mirror_mode) - - # Notify metadata export about SLP metadata when the file is exported - export_request.add_observer(texture_meta_export) - export_request.add_observer(sprite_meta_export) - - handled_graphic_ids.add(graphic_id) - - combined_terrains = full_data_set.combined_terrains.values() - for texture in combined_terrains: - slp_id = texture.get_terrain()["slp_id"].value - srcfile_prefix = texture.get_terrain()["filename"].value - - targetdir = texture.resolve_graphics_location() - source_filename = f"{str(srcfile_prefix)}_00_color.png" - target_filename = f"{texture.get_filename()}.png" - - export_request = MediaExportRequest(MediaType.TERRAIN, - targetdir, - source_filename, - target_filename) - full_data_set.graphics_exports.update({slp_id: export_request}) - - texture_meta_filename = f"{texture.get_filename()}.texture" - texture_meta_export = TextureMetadataExport(targetdir, - texture_meta_filename) - full_data_set.metadata_exports.append(texture_meta_export) - - # Add texture image filename to texture metadata - texture_meta_export.add_imagefile(target_filename) - texture_meta_export.update( - None, - { - f"{target_filename}": { - "size": (512, 512), - "subtex_metadata": [ - { - "x": 0, - "y": 0, - "w": 512, - "h": 512, - "cx": 0, - "cy": 0, - } - ] - }} - ) - - terrain_meta_filename = f"{texture.get_filename()}.terrain" - terrain_meta_export = TerrainMetadataExport(targetdir, - terrain_meta_filename) - full_data_set.metadata_exports.append(terrain_meta_export) - - terrain_meta_export.add_graphics_metadata(target_filename, - texture_meta_filename, - TerrainLayerMode.OFF, - 0, - 0.0, - 0.0, - 1) - - @staticmethod - def create_sound_requests(full_data_set: GenieObjectContainer) -> None: - """ - Create export requests for sounds referenced by CombinedSound objects. - """ - combined_sounds = full_data_set.combined_sounds.values() - - for sound in combined_sounds: - sound_id = sound.get_file_id() - - targetdir = sound.resolve_sound_location() - source_filename = f"{str(sound_id)}.wav" - target_filename = f"{sound.get_filename()}.opus" - - export_request = MediaExportRequest(MediaType.SOUNDS, - targetdir, - source_filename, - target_filename) - - full_data_set.sound_exports.update({sound_id: export_request}) + create_graphics_requests = staticmethod(create_graphics_requests) + create_sound_requests = staticmethod(create_sound_requests) From 6f6c99519dd0ced26f6b8bcecf7199572f64c0b8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 14:30:41 +0200 Subject: [PATCH 134/163] convert: Refactor RoRAbilitySubprocessor into separate files. --- .../processor/conversion/ror/CMakeLists.txt | 2 + .../conversion/ror/ability/CMakeLists.txt | 9 + .../conversion/ror/ability/__init__.py | 5 + .../ror/ability/apply_discrete_effect.py | 352 +++++++ .../ror/ability/game_entity_stance.py | 99 ++ .../ror/ability/production_queue.py | 70 ++ .../conversion/ror/ability/projectile.py | 119 +++ .../conversion/ror/ability/resistance.py | 68 ++ .../ror/ability/shoot_projectile.py | 264 +++++ .../conversion/ror/ability_subprocessor.py | 922 +----------------- 10 files changed, 1000 insertions(+), 910 deletions(-) create mode 100644 openage/convert/processor/conversion/ror/ability/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/ability/__init__.py create mode 100644 openage/convert/processor/conversion/ror/ability/apply_discrete_effect.py create mode 100644 openage/convert/processor/conversion/ror/ability/game_entity_stance.py create mode 100644 openage/convert/processor/conversion/ror/ability/production_queue.py create mode 100644 openage/convert/processor/conversion/ror/ability/projectile.py create mode 100644 openage/convert/processor/conversion/ror/ability/resistance.py create mode 100644 openage/convert/processor/conversion/ror/ability/shoot_projectile.py diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index 521e9e6b32..362cb2080a 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -13,3 +13,5 @@ add_py_modules( upgrade_attribute_subprocessor.py upgrade_resource_subprocessor.py ) + +add_subdirectory(ability) diff --git a/openage/convert/processor/conversion/ror/ability/CMakeLists.txt b/openage/convert/processor/conversion/ror/ability/CMakeLists.txt new file mode 100644 index 0000000000..196b2367f9 --- /dev/null +++ b/openage/convert/processor/conversion/ror/ability/CMakeLists.txt @@ -0,0 +1,9 @@ +add_py_modules( + __init__.py + apply_discrete_effect.py + game_entity_stance.py + production_queue.py + projectile.py + resistance.py + shoot_projectile.py +) diff --git a/openage/convert/processor/conversion/ror/ability/__init__.py b/openage/convert/processor/conversion/ror/ability/__init__.py new file mode 100644 index 0000000000..614701233a --- /dev/null +++ b/openage/convert/processor/conversion/ror/ability/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Derives and adds abilities to game entities created from lines. +""" diff --git a/openage/convert/processor/conversion/ror/ability/apply_discrete_effect.py b/openage/convert/processor/conversion/ror/ability/apply_discrete_effect.py new file mode 100644 index 0000000000..cd5bdbc905 --- /dev/null +++ b/openage/convert/processor/conversion/ror/ability/apply_discrete_effect.py @@ -0,0 +1,352 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ApplyDiscreteEffect ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ...aoc.effect_subprocessor import AoCEffectSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def apply_discrete_effect_ability( + line: GenieGameEntityGroup, + command_id: int, + ranged: bool = False, + projectile: int = -1 +) -> ForwardRef: + """ + Adds the ApplyDiscreteEffect ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + if isinstance(line, GenieVillagerGroup): + current_unit = line.get_units_with_command(command_id)[0] + current_unit_id = current_unit["id0"].value + + else: + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + + head_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + ability_name = command_lookup_dict[command_id][0] + 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_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + if command_id == 104: + # Get animation from commands proceed sprite + unit_commands = current_unit["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + if type_id != command_id: + continue + + ability_animation_id = command["proceed_sprite_id"].value + break + + else: + ability_animation_id = -1 + + else: + ability_animation_id = current_unit["attack_sprite_id"].value + + else: + ability_ref = (f"{game_entity_name}.ShootProjectile." + f"Projectile{projectile}.{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_location = ForwardRef(line, + (f"{game_entity_name}.ShootProjectile." + f"Projectile{projectile}")) + ability_raw_api_object.set_location(ability_location) + + ability_animation_id = -1 + + # Ability properties + properties = {} + + # Animated + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = AoCAbilitySubprocessor.create_animation( + line, + ability_animation_id, + property_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Create custom civ graphics + handled_graphics_set_ids = set() + for civ_group in dataset.civ_groups.values(): + civ = civ_group.civ + civ_id = civ_group.get_id() + + # Only proceed if the civ stores the unit in the line + if current_unit_id not in civ["units"].value.keys(): + continue + + civ_animation_id = civ["units"][current_unit_id]["attack_sprite_id"].value + + if civ_animation_id != ability_animation_id: + # Find the corresponding graphics set + graphics_set_id = -1 + for set_id, items in gset_lookup_dict.items(): + if civ_id in items[0]: + graphics_set_id = set_id + break + + # Check if the object for the animation has been created before + obj_exists = graphics_set_id in handled_graphics_set_ids + if not obj_exists: + handled_graphics_set_ids.add(graphics_set_id) + + obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}{ability_name}" + filename_prefix = (f"{command_lookup_dict[command_id][1]}_" + f"{gset_lookup_dict[graphics_set_id][2]}_") + AoCAbilitySubprocessor.create_civ_animation(line, + civ_group, + civ_animation_id, + f"{ability_ref}.Animated", + obj_prefix, + filename_prefix, + obj_exists) + + # Command Sound + if projectile == -1: + ability_comm_sound_id = current_unit["command_sound_id"].value + + else: + ability_comm_sound_id = -1 + + if ability_comm_sound_id > -1: + property_ref = f"{ability_ref}.CommandSound" + property_raw_api_object = RawAPIObject(property_ref, + "CommandSound", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + sounds_set = [] + + if projectile == -1: + sound_obj_prefix = ability_name + + else: + sound_obj_prefix = "ProjectileAttack" + + sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, + ability_comm_sound_id, + property_ref, + sound_obj_prefix, + "command_") + sounds_set.append(sound_forward_ref) + property_raw_api_object.add_raw_member("sounds", sounds_set, + "engine.ability.property.type.CommandSound") + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + 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 + 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 + 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" + batch_raw_api_object = RawAPIObject(batch_ref, "Batch", dataset.nyan_api_objects) + batch_raw_api_object.add_raw_parent("engine.util.effect_batch.type.UnorderedBatch") + batch_location = ForwardRef(line, ability_ref) + batch_raw_api_object.set_location(batch_location) + + line.add_raw_api_object(batch_raw_api_object) + + # Effects + effects = [] + if command_id == 7: + # Attack + if projectile != 1: + effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref) + + else: + effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref, projectile=1) + + elif command_id == 104: + # TODO: Convert + # effects = AoCEffectSubprocessor.get_convert_effects(line, ability_ref) + pass + + batch_raw_api_object.add_raw_member("effects", + effects, + "engine.util.effect_batch.EffectBatch") + + batch_forward_ref = ForwardRef(line, batch_ref) + ability_raw_api_object.add_raw_member("batches", + [batch_forward_ref], + "engine.ability.type.ApplyDiscreteEffect") + + # Reload time + if projectile == -1: + reload_time = current_unit["attack_speed"].value + + else: + reload_time = 0 + + ability_raw_api_object.add_raw_member("reload_time", + reload_time, + "engine.ability.type.ApplyDiscreteEffect") + + # Application delay + if projectile == -1: + apply_graphic = dataset.genie_graphics[ability_animation_id] + frame_rate = apply_graphic.get_frame_rate() + frame_delay = current_unit["frame_delay"].value + application_delay = frame_rate * frame_delay + + else: + application_delay = 0 + + ability_raw_api_object.add_raw_member("application_delay", + application_delay, + "engine.ability.type.ApplyDiscreteEffect") + + # Allowed types (all buildings/units) + if command_id == 104: + # Convert + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() + ] + + else: + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( + ) + ] + + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.ApplyDiscreteEffect") + + if command_id == 104: + # Convert + blacklisted_entities = [] + for unit_line in dataset.unit_lines.values(): + if unit_line.has_command(104): + # Blacklist other monks + blacklisted_name = name_lookup_dict[unit_line.get_head_unit_id()][0] + blacklisted_entities.append(ForwardRef(unit_line, blacklisted_name)) + continue + + else: + blacklisted_entities = [] + + ability_raw_api_object.add_raw_member("blacklisted_entities", + blacklisted_entities, + "engine.ability.type.ApplyDiscreteEffect") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/ror/ability/game_entity_stance.py b/openage/convert/processor/conversion/ror/ability/game_entity_stance.py new file mode 100644 index 0000000000..7686f8e1f0 --- /dev/null +++ b/openage/convert/processor/conversion/ror/ability/game_entity_stance.py @@ -0,0 +1,99 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the GameEntityStance ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def game_entity_stance_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the GameEntityStance ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + current_unit = line.get_head_unit() + 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}.GameEntityStance" + ability_raw_api_object = RawAPIObject(ability_ref, + "GameEntityStance", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.GameEntityStance") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Stances + search_range = current_unit["search_radius"].value + stance_names = ["Aggressive", "StandGround"] + + # Attacking is preferred + ability_preferences = [] + if line.is_projectile_shooter(): + ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Attack")) + + elif line.is_melee() or line.is_ranged(): + if line.has_command(7): + ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Attack")) + + if line.has_command(105): + ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Heal")) + + # Units are preferred before buildings + type_preferences = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), + ] + + stances = [] + for stance_name in stance_names: + stance_api_ref = f"engine.util.game_entity_stance.type.{stance_name}" + + stance_ref = f"{game_entity_name}.GameEntityStance.{stance_name}" + stance_raw_api_object = RawAPIObject(stance_ref, stance_name, dataset.nyan_api_objects) + stance_raw_api_object.add_raw_parent(stance_api_ref) + stance_location = ForwardRef(line, ability_ref) + stance_raw_api_object.set_location(stance_location) + + # Search range + stance_raw_api_object.add_raw_member("search_range", + search_range, + "engine.util.game_entity_stance.GameEntityStance") + + # Ability preferences + stance_raw_api_object.add_raw_member("ability_preference", + ability_preferences, + "engine.util.game_entity_stance.GameEntityStance") + + # Type preferences + stance_raw_api_object.add_raw_member("type_preference", + type_preferences, + "engine.util.game_entity_stance.GameEntityStance") + + line.add_raw_api_object(stance_raw_api_object) + stance_forward_ref = ForwardRef(line, stance_ref) + stances.append(stance_forward_ref) + + ability_raw_api_object.add_raw_member("stances", + stances, + "engine.ability.type.GameEntityStance") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/ror/ability/production_queue.py b/openage/convert/processor/conversion/ror/ability/production_queue.py new file mode 100644 index 0000000000..fafa62bdf1 --- /dev/null +++ b/openage/convert/processor/conversion/ror/ability/production_queue.py @@ -0,0 +1,70 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ProductionQueue ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def production_queue_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ProductionQueue ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + 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}.ProductionQueue" + ability_raw_api_object = RawAPIObject(ability_ref, + "ProductionQueue", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.ProductionQueue") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Size + size = 22 + + ability_raw_api_object.add_raw_member("size", size, "engine.ability.type.ProductionQueue") + + # Production modes + modes = [] + + mode_name = f"{game_entity_name}.ProvideContingent.CreatablesMode" + mode_raw_api_object = RawAPIObject(mode_name, "CreatablesMode", dataset.nyan_api_objects) + mode_raw_api_object.add_raw_parent("engine.util.production_mode.type.Creatables") + mode_location = ForwardRef(line, ability_ref) + mode_raw_api_object.set_location(mode_location) + + # RoR allows all creatables in production queue + mode_raw_api_object.add_raw_member("exclude", + [], + "engine.util.production_mode.type.Creatables") + + mode_forward_ref = ForwardRef(line, mode_name) + modes.append(mode_forward_ref) + + ability_raw_api_object.add_raw_member("production_modes", + modes, + "engine.ability.type.ProductionQueue") + + line.add_raw_api_object(mode_raw_api_object) + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/ror/ability/projectile.py b/openage/convert/processor/conversion/ror/ability/projectile.py new file mode 100644 index 0000000000..db2dee0c48 --- /dev/null +++ b/openage/convert/processor/conversion/ror/ability/projectile.py @@ -0,0 +1,119 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Projectile ability. +""" +from __future__ import annotations +import typing + +from math import degrees + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def projectile_ability(line: GenieGameEntityGroup, position: int = 0) -> ForwardRef: + """ + Adds a Projectile ability to projectiles in a line. Which projectile should + be added is determined by the 'position' argument. + + :param line: Unit/Building line that gets the ability. + :param position: When 0, gives the first projectile its ability. When 1, the second... + :returns: The forward reference for the ability. + """ + current_unit = line.get_head_unit() + 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] + + # First projectile is mandatory + obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{position}" + ability_ref = f"{game_entity_name}.ShootProjectile.Projectile{position}.Projectile" + ability_raw_api_object = RawAPIObject(ability_ref, + "Projectile", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Projectile") + ability_location = ForwardRef(line, obj_ref) + ability_raw_api_object.set_location(ability_location) + + # Arc + if position == 0: + projectile_id = current_unit["projectile_id0"].value + + else: + raise ValueError(f"Invalid projectile position {position}") + + projectile = dataset.genie_units[projectile_id] + arc = degrees(projectile["projectile_arc"].value) + ability_raw_api_object.add_raw_member("arc", + arc, + "engine.ability.type.Projectile") + + # Accuracy + accuracy_name = (f"{game_entity_name}.ShootProjectile." + f"Projectile{position}.Projectile.Accuracy") + accuracy_raw_api_object = RawAPIObject(accuracy_name, "Accuracy", dataset.nyan_api_objects) + accuracy_raw_api_object.add_raw_parent("engine.util.accuracy.Accuracy") + accuracy_location = ForwardRef(line, ability_ref) + accuracy_raw_api_object.set_location(accuracy_location) + + accuracy_value = current_unit["accuracy"].value + accuracy_raw_api_object.add_raw_member("accuracy", + accuracy_value, + "engine.util.accuracy.Accuracy") + + accuracy_dispersion = 0 + accuracy_raw_api_object.add_raw_member("accuracy_dispersion", + accuracy_dispersion, + "engine.util.accuracy.Accuracy") + dropoff_type = dataset.nyan_api_objects["engine.util.dropoff_type.type.NoDropoff"] + accuracy_raw_api_object.add_raw_member("dispersion_dropoff", + dropoff_type, + "engine.util.accuracy.Accuracy") + + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() + ] + accuracy_raw_api_object.add_raw_member("target_types", + allowed_types, + "engine.util.accuracy.Accuracy") + accuracy_raw_api_object.add_raw_member("blacklisted_entities", + [], + "engine.util.accuracy.Accuracy") + + line.add_raw_api_object(accuracy_raw_api_object) + accuracy_forward_ref = ForwardRef(line, accuracy_name) + ability_raw_api_object.add_raw_member("accuracy", + [accuracy_forward_ref], + "engine.ability.type.Projectile") + + # Target mode + target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.CurrentPosition"] + ability_raw_api_object.add_raw_member("target_mode", + target_mode, + "engine.ability.type.Projectile") + + # Ingore types; buildings are ignored unless targeted + ignore_forward_refs = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object() + ] + ability_raw_api_object.add_raw_member("ignored_types", + ignore_forward_refs, + "engine.ability.type.Projectile") + ability_raw_api_object.add_raw_member("unignored_entities", + [], + "engine.ability.type.Projectile") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/ror/ability/resistance.py b/openage/convert/processor/conversion/ror/ability/resistance.py new file mode 100644 index 0000000000..3fe1f8f7d2 --- /dev/null +++ b/openage/convert/processor/conversion/ror/ability/resistance.py @@ -0,0 +1,68 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Resistance ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, \ + GenieUnitLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.effect_subprocessor import AoCEffectSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def resistance_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Resistance ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + 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}.Resistance" + ability_raw_api_object = RawAPIObject(ability_ref, "Resistance", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Resistance") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Resistances + resistances = [] + resistances.extend(AoCEffectSubprocessor.get_attack_resistances(line, + ability_ref)) + if isinstance(line, (GenieUnitLineGroup, GenieBuildingLineGroup)): + # TODO: Conversion resistance + # resistances.extend(RoREffectSubprocessor.get_convert_resistances(line, + # ability_ref)) + + if isinstance(line, GenieUnitLineGroup) and not line.is_repairable(): + resistances.extend(AoCEffectSubprocessor.get_heal_resistances(line, + ability_ref)) + + if isinstance(line, GenieBuildingLineGroup): + resistances.extend(AoCEffectSubprocessor.get_construct_resistances(line, + ability_ref)) + + if line.is_repairable(): + resistances.extend(AoCEffectSubprocessor.get_repair_resistances(line, + ability_ref)) + + ability_raw_api_object.add_raw_member("resistances", + resistances, + "engine.ability.type.Resistance") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/ror/ability/shoot_projectile.py b/openage/convert/processor/conversion/ror/ability/shoot_projectile.py new file mode 100644 index 0000000000..fa7b0bbd9e --- /dev/null +++ b/openage/convert/processor/conversion/ror/ability/shoot_projectile.py @@ -0,0 +1,264 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ShootProjectile ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> ForwardRef: + """ + Adds the ShootProjectile ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + + ability_name = command_lookup_dict[command_id][0] + + game_entity_name = name_lookup_dict[current_unit_id][0] + 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("engine.ability.type.ShootProjectile") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # 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: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = AoCAbilitySubprocessor.create_animation( + line, + ability_animation_id, + property_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", + animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Command Sound + ability_comm_sound_id = current_unit["command_sound_id"].value + if ability_comm_sound_id > -1: + property_ref = f"{ability_ref}.CommandSound" + property_raw_api_object = RawAPIObject(property_ref, + "CommandSound", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + sounds_set = [] + sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, + ability_comm_sound_id, + property_ref, + ability_name, + "command_") + sounds_set.append(sound_forward_ref) + property_raw_api_object.add_raw_member("sounds", + sounds_set, + "engine.ability.property.type.CommandSound") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Projectile + projectiles = [] + projectile_primary = current_unit["projectile_id0"].value + if projectile_primary > -1: + projectiles.append(ForwardRef(line, + f"{game_entity_name}.ShootProjectile.Projectile0")) + + ability_raw_api_object.add_raw_member("projectiles", + projectiles, + "engine.ability.type.ShootProjectile") + + # Projectile count (does not exist in RoR) + min_projectiles = 1 + max_projectiles = 1 + + ability_raw_api_object.add_raw_member("min_projectiles", + min_projectiles, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("max_projectiles", + max_projectiles, + "engine.ability.type.ShootProjectile") + + # Reload time and delay + reload_time = current_unit["attack_speed"].value + ability_raw_api_object.add_raw_member("reload_time", + reload_time, + "engine.ability.type.ShootProjectile") + + if ability_animation_id > -1: + animation = dataset.genie_graphics[ability_animation_id] + frame_rate = animation.get_frame_rate() + + else: + frame_rate = 0 + + spawn_delay_frames = current_unit["frame_delay"].value + spawn_delay = frame_rate * spawn_delay_frames + ability_raw_api_object.add_raw_member("spawn_delay", + spawn_delay, + "engine.ability.type.ShootProjectile") + + # Projectile delay (unused because RoR has no multiple projectiles) + ability_raw_api_object.add_raw_member("projectile_delay", + 0.0, + "engine.ability.type.ShootProjectile") + + # Turning + if isinstance(line, GenieBuildingLineGroup): + require_turning = False + + else: + require_turning = True + + ability_raw_api_object.add_raw_member("require_turning", + require_turning, + "engine.ability.type.ShootProjectile") + + # Manual aiming + manual_aiming_allowed = line.get_head_unit_id() in (35, 250) + ability_raw_api_object.add_raw_member("manual_aiming_allowed", + manual_aiming_allowed, + "engine.ability.type.ShootProjectile") + + # Spawning area + spawning_area_offset_x = current_unit["weapon_offset"][0].value + spawning_area_offset_y = current_unit["weapon_offset"][1].value + spawning_area_offset_z = current_unit["weapon_offset"][2].value + + ability_raw_api_object.add_raw_member("spawning_area_offset_x", + spawning_area_offset_x, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("spawning_area_offset_y", + spawning_area_offset_y, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("spawning_area_offset_z", + spawning_area_offset_z, + "engine.ability.type.ShootProjectile") + + # Spawn Area (does not exist in RoR) + spawning_area_width = 0 + spawning_area_height = 0 + spawning_area_randomness = 0 + + ability_raw_api_object.add_raw_member("spawning_area_width", + spawning_area_width, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("spawning_area_height", + spawning_area_height, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("spawning_area_randomness", + spawning_area_randomness, + "engine.ability.type.ShootProjectile") + + # Restrictions on targets (only units and buildings allowed) + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() + ] + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.ShootProjectile") + ability_raw_api_object.add_raw_member("blacklisted_entities", + [], + "engine.ability.type.ShootProjectile") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/ror/ability_subprocessor.py b/openage/convert/processor/conversion/ror/ability_subprocessor.py index 89a634bf37..a1f25593ee 100644 --- a/openage/convert/processor/conversion/ror/ability_subprocessor.py +++ b/openage/convert/processor/conversion/ror/ability_subprocessor.py @@ -1,29 +1,15 @@ # Copyright 2020-2025 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-branches,too-many-statements,too-many-locals -# -# TODO: -# pylint: disable=line-too-long """ Derives and adds abilities to lines. Reimplements only abilities that are different from AoC. """ -from __future__ import annotations -import typing - -from math import degrees - -from ....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, \ - GenieVillagerGroup, GenieUnitLineGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ..aoc.ability_subprocessor import AoCAbilitySubprocessor -from ..aoc.effect_subprocessor import AoCEffectSubprocessor - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup +from .ability.apply_discrete_effect import apply_discrete_effect_ability +from .ability.game_entity_stance import game_entity_stance_ability +from .ability.production_queue import production_queue_ability +from .ability.projectile import projectile_ability +from .ability.resistance import resistance_ability +from .ability.shoot_projectile import shoot_projectile_ability class RoRAbilitySubprocessor: @@ -31,893 +17,9 @@ class RoRAbilitySubprocessor: Creates raw API objects for abilities in RoR. """ - @staticmethod - def apply_discrete_effect_ability( - line: GenieGameEntityGroup, - command_id: int, - ranged: bool = False, - projectile: int = -1 - ) -> ForwardRef: - """ - Adds the ApplyDiscreteEffect 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 - """ - if isinstance(line, GenieVillagerGroup): - current_unit = line.get_units_with_command(command_id)[0] - current_unit_id = current_unit["id0"].value - - else: - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - - head_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - ability_name = command_lookup_dict[command_id][0] - 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_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - if command_id == 104: - # Get animation from commands proceed sprite - unit_commands = current_unit["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - if type_id != command_id: - continue - - ability_animation_id = command["proceed_sprite_id"].value - break - - else: - ability_animation_id = -1 - - else: - ability_animation_id = current_unit["attack_sprite_id"].value - - else: - ability_ref = (f"{game_entity_name}.ShootProjectile." - f"Projectile{projectile}.{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_location = ForwardRef(line, - (f"{game_entity_name}.ShootProjectile." - f"Projectile{projectile}")) - ability_raw_api_object.set_location(ability_location) - - ability_animation_id = -1 - - # Ability properties - properties = {} - - # Animated - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - ability_animation_id, - property_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Create custom civ graphics - handled_graphics_set_ids = set() - for civ_group in dataset.civ_groups.values(): - civ = civ_group.civ - civ_id = civ_group.get_id() - - # Only proceed if the civ stores the unit in the line - if current_unit_id not in civ["units"].value.keys(): - continue - - civ_animation_id = civ["units"][current_unit_id]["attack_sprite_id"].value - - if civ_animation_id != ability_animation_id: - # Find the corresponding graphics set - graphics_set_id = -1 - for set_id, items in gset_lookup_dict.items(): - if civ_id in items[0]: - graphics_set_id = set_id - break - - # Check if the object for the animation has been created before - obj_exists = graphics_set_id in handled_graphics_set_ids - if not obj_exists: - handled_graphics_set_ids.add(graphics_set_id) - - obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}{ability_name}" - filename_prefix = (f"{command_lookup_dict[command_id][1]}_" - f"{gset_lookup_dict[graphics_set_id][2]}_") - AoCAbilitySubprocessor.create_civ_animation(line, - civ_group, - civ_animation_id, - f"{ability_ref}.Animated", - obj_prefix, - filename_prefix, - obj_exists) - - # Command Sound - if projectile == -1: - ability_comm_sound_id = current_unit["command_sound_id"].value - - else: - ability_comm_sound_id = -1 - - if ability_comm_sound_id > -1: - property_ref = f"{ability_ref}.CommandSound" - property_raw_api_object = RawAPIObject(property_ref, - "CommandSound", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - sounds_set = [] - - if projectile == -1: - sound_obj_prefix = ability_name - - else: - sound_obj_prefix = "ProjectileAttack" - - sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, - ability_comm_sound_id, - property_ref, - sound_obj_prefix, - "command_") - sounds_set.append(sound_forward_ref) - property_raw_api_object.add_raw_member("sounds", sounds_set, - "engine.ability.property.type.CommandSound") - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - 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 - 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 - 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" - batch_raw_api_object = RawAPIObject(batch_ref, "Batch", dataset.nyan_api_objects) - batch_raw_api_object.add_raw_parent("engine.util.effect_batch.type.UnorderedBatch") - batch_location = ForwardRef(line, ability_ref) - batch_raw_api_object.set_location(batch_location) - - line.add_raw_api_object(batch_raw_api_object) - - # Effects - effects = [] - if command_id == 7: - # Attack - if projectile != 1: - effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref) - - else: - effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref, projectile=1) - - elif command_id == 104: - # TODO: Convert - # effects = AoCEffectSubprocessor.get_convert_effects(line, ability_ref) - pass - - batch_raw_api_object.add_raw_member("effects", - effects, - "engine.util.effect_batch.EffectBatch") - - batch_forward_ref = ForwardRef(line, batch_ref) - ability_raw_api_object.add_raw_member("batches", - [batch_forward_ref], - "engine.ability.type.ApplyDiscreteEffect") - - # Reload time - if projectile == -1: - reload_time = current_unit["attack_speed"].value - - else: - reload_time = 0 - - ability_raw_api_object.add_raw_member("reload_time", - reload_time, - "engine.ability.type.ApplyDiscreteEffect") - - # Application delay - if projectile == -1: - apply_graphic = dataset.genie_graphics[ability_animation_id] - frame_rate = apply_graphic.get_frame_rate() - frame_delay = current_unit["frame_delay"].value - application_delay = frame_rate * frame_delay - - else: - application_delay = 0 - - ability_raw_api_object.add_raw_member("application_delay", - application_delay, - "engine.ability.type.ApplyDiscreteEffect") - - # Allowed types (all buildings/units) - if command_id == 104: - # Convert - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() - ] - - else: - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( - ) - ] - - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.ApplyDiscreteEffect") - - if command_id == 104: - # Convert - blacklisted_entities = [] - for unit_line in dataset.unit_lines.values(): - if unit_line.has_command(104): - # Blacklist other monks - blacklisted_name = name_lookup_dict[unit_line.get_head_unit_id()][0] - blacklisted_entities.append(ForwardRef(unit_line, blacklisted_name)) - continue - - else: - blacklisted_entities = [] - - ability_raw_api_object.add_raw_member("blacklisted_entities", - blacklisted_entities, - "engine.ability.type.ApplyDiscreteEffect") - - 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 game_entity_stance_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the GameEntityStance 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 = line.get_head_unit() - 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}.GameEntityStance" - ability_raw_api_object = RawAPIObject(ability_ref, - "GameEntityStance", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.GameEntityStance") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Stances - search_range = current_unit["search_radius"].value - stance_names = ["Aggressive", "StandGround"] - - # Attacking is preferred - ability_preferences = [] - if line.is_projectile_shooter(): - ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Attack")) - - elif line.is_melee() or line.is_ranged(): - if line.has_command(7): - ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Attack")) - - if line.has_command(105): - ability_preferences.append(ForwardRef(line, f"{game_entity_name}.Heal")) - - # Units are preferred before buildings - type_preferences = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), - ] - - stances = [] - for stance_name in stance_names: - stance_api_ref = f"engine.util.game_entity_stance.type.{stance_name}" - - stance_ref = f"{game_entity_name}.GameEntityStance.{stance_name}" - stance_raw_api_object = RawAPIObject(stance_ref, stance_name, dataset.nyan_api_objects) - stance_raw_api_object.add_raw_parent(stance_api_ref) - stance_location = ForwardRef(line, ability_ref) - stance_raw_api_object.set_location(stance_location) - - # Search range - stance_raw_api_object.add_raw_member("search_range", - search_range, - "engine.util.game_entity_stance.GameEntityStance") - - # Ability preferences - stance_raw_api_object.add_raw_member("ability_preference", - ability_preferences, - "engine.util.game_entity_stance.GameEntityStance") - - # Type preferences - stance_raw_api_object.add_raw_member("type_preference", - type_preferences, - "engine.util.game_entity_stance.GameEntityStance") - - line.add_raw_api_object(stance_raw_api_object) - stance_forward_ref = ForwardRef(line, stance_ref) - stances.append(stance_forward_ref) - - ability_raw_api_object.add_raw_member("stances", - stances, - "engine.ability.type.GameEntityStance") - - 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 production_queue_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ProductionQueue 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}.ProductionQueue" - ability_raw_api_object = RawAPIObject(ability_ref, - "ProductionQueue", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.ProductionQueue") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Size - size = 22 - - ability_raw_api_object.add_raw_member("size", size, "engine.ability.type.ProductionQueue") - - # Production modes - modes = [] - - mode_name = f"{game_entity_name}.ProvideContingent.CreatablesMode" - mode_raw_api_object = RawAPIObject(mode_name, "CreatablesMode", dataset.nyan_api_objects) - mode_raw_api_object.add_raw_parent("engine.util.production_mode.type.Creatables") - mode_location = ForwardRef(line, ability_ref) - mode_raw_api_object.set_location(mode_location) - - # RoR allows all creatables in production queue - mode_raw_api_object.add_raw_member("exclude", - [], - "engine.util.production_mode.type.Creatables") - - mode_forward_ref = ForwardRef(line, mode_name) - modes.append(mode_forward_ref) - - ability_raw_api_object.add_raw_member("production_modes", - modes, - "engine.ability.type.ProductionQueue") - - line.add_raw_api_object(mode_raw_api_object) - 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 projectile_ability(line: GenieGameEntityGroup, position: int = 0) -> ForwardRef: - """ - Adds a Projectile ability to projectiles in a line. Which projectile should - be added is determined by the 'position' argument. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param position: When 0, gives the first projectile its ability. When 1, the second... - :type position: int - :returns: The forward reference for the ability. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - current_unit = line.get_head_unit() - 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] - - # First projectile is mandatory - obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{position}" - ability_ref = f"{game_entity_name}.ShootProjectile.Projectile{position}.Projectile" - ability_raw_api_object = RawAPIObject(ability_ref, - "Projectile", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Projectile") - ability_location = ForwardRef(line, obj_ref) - ability_raw_api_object.set_location(ability_location) - - # Arc - if position == 0: - projectile_id = current_unit["projectile_id0"].value - - else: - raise ValueError(f"Invalid projectile position {position}") - - projectile = dataset.genie_units[projectile_id] - arc = degrees(projectile["projectile_arc"].value) - ability_raw_api_object.add_raw_member("arc", - arc, - "engine.ability.type.Projectile") - - # Accuracy - accuracy_name = (f"{game_entity_name}.ShootProjectile." - f"Projectile{position}.Projectile.Accuracy") - accuracy_raw_api_object = RawAPIObject(accuracy_name, "Accuracy", dataset.nyan_api_objects) - accuracy_raw_api_object.add_raw_parent("engine.util.accuracy.Accuracy") - accuracy_location = ForwardRef(line, ability_ref) - accuracy_raw_api_object.set_location(accuracy_location) - - accuracy_value = current_unit["accuracy"].value - accuracy_raw_api_object.add_raw_member("accuracy", - accuracy_value, - "engine.util.accuracy.Accuracy") - - accuracy_dispersion = 0 - accuracy_raw_api_object.add_raw_member("accuracy_dispersion", - accuracy_dispersion, - "engine.util.accuracy.Accuracy") - dropoff_type = dataset.nyan_api_objects["engine.util.dropoff_type.type.NoDropoff"] - accuracy_raw_api_object.add_raw_member("dispersion_dropoff", - dropoff_type, - "engine.util.accuracy.Accuracy") - - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() - ] - accuracy_raw_api_object.add_raw_member("target_types", - allowed_types, - "engine.util.accuracy.Accuracy") - accuracy_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.util.accuracy.Accuracy") - - line.add_raw_api_object(accuracy_raw_api_object) - accuracy_forward_ref = ForwardRef(line, accuracy_name) - ability_raw_api_object.add_raw_member("accuracy", - [accuracy_forward_ref], - "engine.ability.type.Projectile") - - # Target mode - target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.CurrentPosition"] - ability_raw_api_object.add_raw_member("target_mode", - target_mode, - "engine.ability.type.Projectile") - - # Ingore types; buildings are ignored unless targeted - ignore_forward_refs = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object() - ] - ability_raw_api_object.add_raw_member("ignored_types", - ignore_forward_refs, - "engine.ability.type.Projectile") - ability_raw_api_object.add_raw_member("unignored_entities", - [], - "engine.ability.type.Projectile") - - 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 resistance_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Resistance 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}.Resistance" - ability_raw_api_object = RawAPIObject(ability_ref, "Resistance", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Resistance") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Resistances - resistances = [] - resistances.extend(AoCEffectSubprocessor.get_attack_resistances(line, - ability_ref)) - if isinstance(line, (GenieUnitLineGroup, GenieBuildingLineGroup)): - # TODO: Conversion resistance - # resistances.extend(RoREffectSubprocessor.get_convert_resistances(line, - # ability_ref)) - - if isinstance(line, GenieUnitLineGroup) and not line.is_repairable(): - resistances.extend(AoCEffectSubprocessor.get_heal_resistances(line, - ability_ref)) - - if isinstance(line, GenieBuildingLineGroup): - resistances.extend(AoCEffectSubprocessor.get_construct_resistances(line, - ability_ref)) - - if line.is_repairable(): - resistances.extend(AoCEffectSubprocessor.get_repair_resistances(line, - ability_ref)) - - ability_raw_api_object.add_raw_member("resistances", - resistances, - "engine.ability.type.Resistance") - - 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 shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> ForwardRef: - """ - Adds the ShootProjectile 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - - ability_name = command_lookup_dict[command_id][0] - - game_entity_name = name_lookup_dict[current_unit_id][0] - 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("engine.ability.type.ShootProjectile") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # 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: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - ability_animation_id, - property_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", - animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Command Sound - ability_comm_sound_id = current_unit["command_sound_id"].value - if ability_comm_sound_id > -1: - property_ref = f"{ability_ref}.CommandSound" - property_raw_api_object = RawAPIObject(property_ref, - "CommandSound", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - sounds_set = [] - sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, - ability_comm_sound_id, - property_ref, - ability_name, - "command_") - sounds_set.append(sound_forward_ref) - property_raw_api_object.add_raw_member("sounds", - sounds_set, - "engine.ability.property.type.CommandSound") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Projectile - projectiles = [] - projectile_primary = current_unit["projectile_id0"].value - if projectile_primary > -1: - projectiles.append(ForwardRef(line, - f"{game_entity_name}.ShootProjectile.Projectile0")) - - ability_raw_api_object.add_raw_member("projectiles", - projectiles, - "engine.ability.type.ShootProjectile") - - # Projectile count (does not exist in RoR) - min_projectiles = 1 - max_projectiles = 1 - - ability_raw_api_object.add_raw_member("min_projectiles", - min_projectiles, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("max_projectiles", - max_projectiles, - "engine.ability.type.ShootProjectile") - - # Reload time and delay - reload_time = current_unit["attack_speed"].value - ability_raw_api_object.add_raw_member("reload_time", - reload_time, - "engine.ability.type.ShootProjectile") - - if ability_animation_id > -1: - animation = dataset.genie_graphics[ability_animation_id] - frame_rate = animation.get_frame_rate() - - else: - frame_rate = 0 - - spawn_delay_frames = current_unit["frame_delay"].value - spawn_delay = frame_rate * spawn_delay_frames - ability_raw_api_object.add_raw_member("spawn_delay", - spawn_delay, - "engine.ability.type.ShootProjectile") - - # Projectile delay (unused because RoR has no multiple projectiles) - ability_raw_api_object.add_raw_member("projectile_delay", - 0.0, - "engine.ability.type.ShootProjectile") - - # Turning - if isinstance(line, GenieBuildingLineGroup): - require_turning = False - - else: - require_turning = True - - ability_raw_api_object.add_raw_member("require_turning", - require_turning, - "engine.ability.type.ShootProjectile") - - # Manual aiming - manual_aiming_allowed = line.get_head_unit_id() in (35, 250) - ability_raw_api_object.add_raw_member("manual_aiming_allowed", - manual_aiming_allowed, - "engine.ability.type.ShootProjectile") - - # Spawning area - spawning_area_offset_x = current_unit["weapon_offset"][0].value - spawning_area_offset_y = current_unit["weapon_offset"][1].value - spawning_area_offset_z = current_unit["weapon_offset"][2].value - - ability_raw_api_object.add_raw_member("spawning_area_offset_x", - spawning_area_offset_x, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("spawning_area_offset_y", - spawning_area_offset_y, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("spawning_area_offset_z", - spawning_area_offset_z, - "engine.ability.type.ShootProjectile") - - # Spawn Area (does not exist in RoR) - spawning_area_width = 0 - spawning_area_height = 0 - spawning_area_randomness = 0 - - ability_raw_api_object.add_raw_member("spawning_area_width", - spawning_area_width, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("spawning_area_height", - spawning_area_height, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("spawning_area_randomness", - spawning_area_randomness, - "engine.ability.type.ShootProjectile") - - # Restrictions on targets (only units and buildings allowed) - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() - ] - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.ShootProjectile") - ability_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.ability.type.ShootProjectile") - - line.add_raw_api_object(ability_raw_api_object) - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref + apply_discrete_effect_ability = staticmethod(apply_discrete_effect_ability) + game_entity_stance_ability = staticmethod(game_entity_stance_ability) + production_queue_ability = staticmethod(production_queue_ability) + projectile_ability = staticmethod(projectile_ability) + resistance_ability = staticmethod(resistance_ability) + shoot_projectile_ability = staticmethod(shoot_projectile_ability) From 5100eb2092287ba4a11d8f153b3e2679f9f28555 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 14:34:47 +0200 Subject: [PATCH 135/163] convert: Refactor RoRAuxiliarySubprocessor into separate files. --- .../processor/conversion/ror/CMakeLists.txt | 1 + .../conversion/ror/auxiliary/CMakeLists.txt | 4 + .../conversion/ror/auxiliary/__init__.py | 6 + .../ror/auxiliary/creatable_game_entity.py | 331 +++++++++++++++++ .../conversion/ror/auxiliary_subprocessor.py | 333 +----------------- 5 files changed, 345 insertions(+), 330 deletions(-) create mode 100644 openage/convert/processor/conversion/ror/auxiliary/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/auxiliary/__init__.py create mode 100644 openage/convert/processor/conversion/ror/auxiliary/creatable_game_entity.py diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index 362cb2080a..df18000756 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -15,3 +15,4 @@ add_py_modules( ) add_subdirectory(ability) +add_subdirectory(auxiliary) diff --git a/openage/convert/processor/conversion/ror/auxiliary/CMakeLists.txt b/openage/convert/processor/conversion/ror/auxiliary/CMakeLists.txt new file mode 100644 index 0000000000..9f7944b14d --- /dev/null +++ b/openage/convert/processor/conversion/ror/auxiliary/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + creatable_game_entity.py +) diff --git a/openage/convert/processor/conversion/ror/auxiliary/__init__.py b/openage/convert/processor/conversion/ror/auxiliary/__init__.py new file mode 100644 index 0000000000..d2825e251d --- /dev/null +++ b/openage/convert/processor/conversion/ror/auxiliary/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Derives creatables or researchables objects from unit lines, techs +or other objects. +""" diff --git a/openage/convert/processor/conversion/ror/auxiliary/creatable_game_entity.py b/openage/convert/processor/conversion/ror/auxiliary/creatable_game_entity.py new file mode 100644 index 0000000000..b45335c9fd --- /dev/null +++ b/openage/convert/processor/conversion/ror/auxiliary/creatable_game_entity.py @@ -0,0 +1,331 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for creatables (units). +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ + GenieBuildingLineGroup, GenieUnitLineGroup +from .....entity_object.conversion.combined_sound import CombinedSound +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_creatable_game_entity(line: GenieGameEntityGroup) -> None: + """ + Creates the CreatableGameEntity object for a unit/building line. In comparison + to the AoC version, ths replaces some unit class IDs and removes garrison + placement modes. + + :param line: Unit/Building line. + """ + if isinstance(line, GenieVillagerGroup): + current_unit = line.variants[0].line[0] + + else: + current_unit = line.line[0] + + 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] + + obj_ref = f"{game_entity_name}.CreatableGameEntity" + obj_name = f"{game_entity_name}Creatable" + creatable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) + creatable_raw_api_object.add_raw_parent("engine.util.create.CreatableGameEntity") + + # Get train location of line + train_location_id = line.get_train_location_id() + if isinstance(line, GenieBuildingLineGroup): + train_location = dataset.unit_lines[train_location_id] + train_location_name = name_lookup_dict[train_location_id][0] + + else: + train_location = dataset.building_lines[train_location_id] + train_location_name = name_lookup_dict[train_location_id][0] + + # Add object to the train location's Create ability + creatable_location = ForwardRef(train_location, + f"{train_location_name}.Create") + + creatable_raw_api_object.set_location(creatable_location) + + # Game Entity + game_entity_forward_ref = ForwardRef(line, game_entity_name) + creatable_raw_api_object.add_raw_member("game_entity", + game_entity_forward_ref, + "engine.util.create.CreatableGameEntity") + + # TODO: Variants + variants_set = [] + + creatable_raw_api_object.add_raw_member("variants", variants_set, + "engine.util.create.CreatableGameEntity") + + # Cost (construction) + cost_name = f"{game_entity_name}.CreatableGameEntity.{game_entity_name}Cost" + cost_raw_api_object = RawAPIObject(cost_name, + f"{game_entity_name}Cost", + dataset.nyan_api_objects) + cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") + creatable_forward_ref = ForwardRef(line, obj_ref) + cost_raw_api_object.set_location(creatable_forward_ref) + + payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] + cost_raw_api_object.add_raw_member("payment_mode", + payment_mode, + "engine.util.cost.Cost") + + if isinstance(line, GenieBuildingLineGroup) or line.get_class_id() in (2, 13, 20, 21, 22): + # Cost (repair) for buildings + cost_repair_name = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}RepairCost") + cost_repair_raw_api_object = RawAPIObject(cost_repair_name, + f"{game_entity_name}RepairCost", + dataset.nyan_api_objects) + cost_repair_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") + creatable_forward_ref = ForwardRef(line, obj_ref) + cost_repair_raw_api_object.set_location(creatable_forward_ref) + + payment_repair_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Adaptive"] + cost_repair_raw_api_object.add_raw_member("payment_mode", + payment_repair_mode, + "engine.util.cost.Cost") + line.add_raw_api_object(cost_repair_raw_api_object) + + cost_amounts = [] + cost_repair_amounts = [] + for resource_amount in current_unit["resource_cost"].value: + resource_id = resource_amount["type_id"].value + + resource = None + resource_name = "" + if resource_id == -1: + # Not a valid resource + continue + + if resource_id == 0: + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + resource_name = "Food" + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() + resource_name = "Wood" + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( + ) + resource_name = "Stone" + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() + resource_name = "Gold" + + else: + # Other resource ids are handled differently + continue + + # Skip resources that are only expected to be there + if not resource_amount["enabled"].value: + continue + + amount = resource_amount["amount"].value + + cost_amount_name = f"{cost_name}.{resource_name}Amount" + cost_amount = RawAPIObject(cost_amount_name, + f"{resource_name}Amount", + dataset.nyan_api_objects) + cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") + cost_forward_ref = ForwardRef(line, cost_name) + cost_amount.set_location(cost_forward_ref) + + cost_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + cost_amount.add_raw_member("amount", + amount, + "engine.util.resource.ResourceAmount") + + cost_amount_forward_ref = ForwardRef(line, cost_amount_name) + cost_amounts.append(cost_amount_forward_ref) + line.add_raw_api_object(cost_amount) + + if isinstance(line, GenieBuildingLineGroup) or\ + line.get_class_id() in (2, 13, 20, 21, 22): + # Cost for repairing = half of the construction cost + cost_amount_name = f"{cost_repair_name}.{resource_name}Amount" + cost_amount = RawAPIObject(cost_amount_name, + f"{resource_name}Amount", + dataset.nyan_api_objects) + cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") + cost_forward_ref = ForwardRef(line, cost_repair_name) + cost_amount.set_location(cost_forward_ref) + + cost_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + cost_amount.add_raw_member("amount", + amount / 2, + "engine.util.resource.ResourceAmount") + + cost_amount_forward_ref = ForwardRef(line, cost_amount_name) + cost_repair_amounts.append(cost_amount_forward_ref) + line.add_raw_api_object(cost_amount) + + cost_raw_api_object.add_raw_member("amount", + cost_amounts, + "engine.util.cost.type.ResourceCost") + + if isinstance(line, GenieBuildingLineGroup) or line.get_class_id() in (2, 13, 20, 21, 22): + cost_repair_raw_api_object.add_raw_member("amount", + cost_repair_amounts, + "engine.util.cost.type.ResourceCost") + + cost_forward_ref = ForwardRef(line, cost_name) + creatable_raw_api_object.add_raw_member("cost", + cost_forward_ref, + "engine.util.create.CreatableGameEntity") + # Creation time + if isinstance(line, GenieUnitLineGroup): + creation_time = current_unit["creation_time"].value + + else: + # Buildings are created immediately + creation_time = 0 + + creatable_raw_api_object.add_raw_member("creation_time", + creation_time, + "engine.util.create.CreatableGameEntity") + + # Creation sound + creation_sound_id = current_unit["train_sound_id"].value + + # Create sound object + obj_name = f"{game_entity_name}.CreatableGameEntity.Sound" + sound_raw_api_object = RawAPIObject(obj_name, "CreationSound", + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(line, obj_ref) + sound_raw_api_object.set_location(sound_location) + + # Search for the sound if it exists + creation_sounds = [] + if creation_sound_id > -1: + # Creation sound should be civ agnostic + genie_sound = dataset.genie_sounds[creation_sound_id] + file_id = genie_sound.get_sounds(civ_id=-1)[0] + + if file_id in dataset.combined_sounds: + creation_sound = dataset.combined_sounds[file_id] + creation_sound.add_reference(sound_raw_api_object) + + else: + creation_sound = CombinedSound(creation_sound_id, + file_id, + f"creation_sound_{creation_sound_id}", + dataset) + dataset.combined_sounds.update({file_id: creation_sound}) + creation_sound.add_reference(sound_raw_api_object) + + creation_sounds.append(creation_sound) + + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + creation_sounds, + "engine.util.sound.Sound") + + sound_forward_ref = ForwardRef(line, obj_name) + creatable_raw_api_object.add_raw_member("creation_sounds", + [sound_forward_ref], + "engine.util.create.CreatableGameEntity") + + line.add_raw_api_object(sound_raw_api_object) + + # Condition + unlock_conditions = [] + enabling_research_id = line.get_enabling_research_id() + if enabling_research_id > -1: + unlock_conditions.extend(AoCAuxiliarySubprocessor.get_condition(line, + obj_ref, + enabling_research_id)) + + creatable_raw_api_object.add_raw_member("condition", + unlock_conditions, + "engine.util.create.CreatableGameEntity") + + # Placement modes + placement_modes = [] + if isinstance(line, GenieBuildingLineGroup): + # Buildings are placed on the map + # Place mode + obj_name = f"{game_entity_name}.CreatableGameEntity.Place" + place_raw_api_object = RawAPIObject(obj_name, + "Place", + dataset.nyan_api_objects) + place_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Place") + place_location = ForwardRef(line, + f"{game_entity_name}.CreatableGameEntity") + place_raw_api_object.set_location(place_location) + + # Tile snap distance (uses 1.0 for grid placement) + place_raw_api_object.add_raw_member("tile_snap_distance", + 1.0, + "engine.util.placement_mode.type.Place") + # Clearance size + clearance_size_x = current_unit["clearance_size_x"].value + clearance_size_y = current_unit["clearance_size_y"].value + place_raw_api_object.add_raw_member("clearance_size_x", + clearance_size_x, + "engine.util.placement_mode.type.Place") + place_raw_api_object.add_raw_member("clearance_size_y", + clearance_size_y, + "engine.util.placement_mode.type.Place") + + # Allow rotation + place_raw_api_object.add_raw_member("allow_rotation", + True, + "engine.util.placement_mode.type.Place") + + # Max elevation difference + elevation_mode = current_unit["elevation_mode"].value + if elevation_mode == 2: + max_elevation_difference = 0 + + elif elevation_mode == 3: + max_elevation_difference = 1 + + else: + max_elevation_difference = MemberSpecialValue.NYAN_INF + + place_raw_api_object.add_raw_member("max_elevation_difference", + max_elevation_difference, + "engine.util.placement_mode.type.Place") + + line.add_raw_api_object(place_raw_api_object) + + place_forward_ref = ForwardRef(line, obj_name) + placement_modes.append(place_forward_ref) + + else: + placement_modes.append( + dataset.nyan_api_objects["engine.util.placement_mode.type.Eject"]) + + creatable_raw_api_object.add_raw_member("placement_modes", + placement_modes, + "engine.util.create.CreatableGameEntity") + + line.add_raw_api_object(creatable_raw_api_object) + line.add_raw_api_object(cost_raw_api_object) diff --git a/openage/convert/processor/conversion/ror/auxiliary_subprocessor.py b/openage/convert/processor/conversion/ror/auxiliary_subprocessor.py index 652e80fca0..d7a83ab355 100644 --- a/openage/convert/processor/conversion/ror/auxiliary_subprocessor.py +++ b/openage/convert/processor/conversion/ror/auxiliary_subprocessor.py @@ -1,26 +1,10 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=line-too-long,too-many-locals,too-many-branches,too-many-statements -# pylint: disable=too-few-public-methods +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Derives complex auxiliary objects from unit lines, techs or other objects. """ -from __future__ import annotations -import typing - -from .....nyan.nyan_structs import MemberSpecialValue -from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ - GenieBuildingLineGroup, GenieUnitLineGroup -from ....entity_object.conversion.combined_sound import CombinedSound -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ..aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup +from .auxiliary.creatable_game_entity import get_creatable_game_entity class RoRAuxiliarySubprocessor: @@ -28,315 +12,4 @@ class RoRAuxiliarySubprocessor: Creates complexer auxiliary raw API objects for abilities in RoR. """ - @staticmethod - def get_creatable_game_entity(line: GenieGameEntityGroup) -> None: - """ - Creates the CreatableGameEntity object for a unit/building line. In comparison - to the AoC version, ths replaces some unit class IDs and removes garrison - placement modes. - - :param line: Unit/Building line. - :type line: ...dataformat.converter_object.ConverterObjectGroup - """ - if isinstance(line, GenieVillagerGroup): - current_unit = line.variants[0].line[0] - - else: - current_unit = line.line[0] - - 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] - - obj_ref = f"{game_entity_name}.CreatableGameEntity" - obj_name = f"{game_entity_name}Creatable" - creatable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) - creatable_raw_api_object.add_raw_parent("engine.util.create.CreatableGameEntity") - - # Get train location of line - train_location_id = line.get_train_location_id() - if isinstance(line, GenieBuildingLineGroup): - train_location = dataset.unit_lines[train_location_id] - train_location_name = name_lookup_dict[train_location_id][0] - - else: - train_location = dataset.building_lines[train_location_id] - train_location_name = name_lookup_dict[train_location_id][0] - - # Add object to the train location's Create ability - creatable_location = ForwardRef(train_location, - f"{train_location_name}.Create") - - creatable_raw_api_object.set_location(creatable_location) - - # Game Entity - game_entity_forward_ref = ForwardRef(line, game_entity_name) - creatable_raw_api_object.add_raw_member("game_entity", - game_entity_forward_ref, - "engine.util.create.CreatableGameEntity") - - # TODO: Variants - variants_set = [] - - creatable_raw_api_object.add_raw_member("variants", variants_set, - "engine.util.create.CreatableGameEntity") - - # Cost (construction) - cost_name = f"{game_entity_name}.CreatableGameEntity.{game_entity_name}Cost" - cost_raw_api_object = RawAPIObject(cost_name, - f"{game_entity_name}Cost", - dataset.nyan_api_objects) - cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") - creatable_forward_ref = ForwardRef(line, obj_ref) - cost_raw_api_object.set_location(creatable_forward_ref) - - payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] - cost_raw_api_object.add_raw_member("payment_mode", - payment_mode, - "engine.util.cost.Cost") - - if isinstance(line, GenieBuildingLineGroup) or line.get_class_id() in (2, 13, 20, 21, 22): - # Cost (repair) for buildings - cost_repair_name = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}RepairCost") - cost_repair_raw_api_object = RawAPIObject(cost_repair_name, - f"{game_entity_name}RepairCost", - dataset.nyan_api_objects) - cost_repair_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") - creatable_forward_ref = ForwardRef(line, obj_ref) - cost_repair_raw_api_object.set_location(creatable_forward_ref) - - payment_repair_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Adaptive"] - cost_repair_raw_api_object.add_raw_member("payment_mode", - payment_repair_mode, - "engine.util.cost.Cost") - line.add_raw_api_object(cost_repair_raw_api_object) - - cost_amounts = [] - cost_repair_amounts = [] - for resource_amount in current_unit["resource_cost"].value: - resource_id = resource_amount["type_id"].value - - resource = None - resource_name = "" - if resource_id == -1: - # Not a valid resource - continue - - if resource_id == 0: - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - resource_name = "Food" - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() - resource_name = "Wood" - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object( - ) - resource_name = "Stone" - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() - resource_name = "Gold" - - else: - # Other resource ids are handled differently - continue - - # Skip resources that are only expected to be there - if not resource_amount["enabled"].value: - continue - - amount = resource_amount["amount"].value - - cost_amount_name = f"{cost_name}.{resource_name}Amount" - cost_amount = RawAPIObject(cost_amount_name, - f"{resource_name}Amount", - dataset.nyan_api_objects) - cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") - cost_forward_ref = ForwardRef(line, cost_name) - cost_amount.set_location(cost_forward_ref) - - cost_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - cost_amount.add_raw_member("amount", - amount, - "engine.util.resource.ResourceAmount") - - cost_amount_forward_ref = ForwardRef(line, cost_amount_name) - cost_amounts.append(cost_amount_forward_ref) - line.add_raw_api_object(cost_amount) - - if isinstance(line, GenieBuildingLineGroup) or\ - line.get_class_id() in (2, 13, 20, 21, 22): - # Cost for repairing = half of the construction cost - cost_amount_name = f"{cost_repair_name}.{resource_name}Amount" - cost_amount = RawAPIObject(cost_amount_name, - f"{resource_name}Amount", - dataset.nyan_api_objects) - cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") - cost_forward_ref = ForwardRef(line, cost_repair_name) - cost_amount.set_location(cost_forward_ref) - - cost_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - cost_amount.add_raw_member("amount", - amount / 2, - "engine.util.resource.ResourceAmount") - - cost_amount_forward_ref = ForwardRef(line, cost_amount_name) - cost_repair_amounts.append(cost_amount_forward_ref) - line.add_raw_api_object(cost_amount) - - cost_raw_api_object.add_raw_member("amount", - cost_amounts, - "engine.util.cost.type.ResourceCost") - - if isinstance(line, GenieBuildingLineGroup) or line.get_class_id() in (2, 13, 20, 21, 22): - cost_repair_raw_api_object.add_raw_member("amount", - cost_repair_amounts, - "engine.util.cost.type.ResourceCost") - - cost_forward_ref = ForwardRef(line, cost_name) - creatable_raw_api_object.add_raw_member("cost", - cost_forward_ref, - "engine.util.create.CreatableGameEntity") - # Creation time - if isinstance(line, GenieUnitLineGroup): - creation_time = current_unit["creation_time"].value - - else: - # Buildings are created immediately - creation_time = 0 - - creatable_raw_api_object.add_raw_member("creation_time", - creation_time, - "engine.util.create.CreatableGameEntity") - - # Creation sound - creation_sound_id = current_unit["train_sound_id"].value - - # Create sound object - obj_name = f"{game_entity_name}.CreatableGameEntity.Sound" - sound_raw_api_object = RawAPIObject(obj_name, "CreationSound", - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(line, obj_ref) - sound_raw_api_object.set_location(sound_location) - - # Search for the sound if it exists - creation_sounds = [] - if creation_sound_id > -1: - # Creation sound should be civ agnostic - genie_sound = dataset.genie_sounds[creation_sound_id] - file_id = genie_sound.get_sounds(civ_id=-1)[0] - - if file_id in dataset.combined_sounds: - creation_sound = dataset.combined_sounds[file_id] - creation_sound.add_reference(sound_raw_api_object) - - else: - creation_sound = CombinedSound(creation_sound_id, - file_id, - f"creation_sound_{creation_sound_id}", - dataset) - dataset.combined_sounds.update({file_id: creation_sound}) - creation_sound.add_reference(sound_raw_api_object) - - creation_sounds.append(creation_sound) - - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - creation_sounds, - "engine.util.sound.Sound") - - sound_forward_ref = ForwardRef(line, obj_name) - creatable_raw_api_object.add_raw_member("creation_sounds", - [sound_forward_ref], - "engine.util.create.CreatableGameEntity") - - line.add_raw_api_object(sound_raw_api_object) - - # Condition - unlock_conditions = [] - enabling_research_id = line.get_enabling_research_id() - if enabling_research_id > -1: - unlock_conditions.extend(AoCAuxiliarySubprocessor.get_condition(line, - obj_ref, - enabling_research_id)) - - creatable_raw_api_object.add_raw_member("condition", - unlock_conditions, - "engine.util.create.CreatableGameEntity") - - # Placement modes - placement_modes = [] - if isinstance(line, GenieBuildingLineGroup): - # Buildings are placed on the map - # Place mode - obj_name = f"{game_entity_name}.CreatableGameEntity.Place" - place_raw_api_object = RawAPIObject(obj_name, - "Place", - dataset.nyan_api_objects) - place_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Place") - place_location = ForwardRef(line, - f"{game_entity_name}.CreatableGameEntity") - place_raw_api_object.set_location(place_location) - - # Tile snap distance (uses 1.0 for grid placement) - place_raw_api_object.add_raw_member("tile_snap_distance", - 1.0, - "engine.util.placement_mode.type.Place") - # Clearance size - clearance_size_x = current_unit["clearance_size_x"].value - clearance_size_y = current_unit["clearance_size_y"].value - place_raw_api_object.add_raw_member("clearance_size_x", - clearance_size_x, - "engine.util.placement_mode.type.Place") - place_raw_api_object.add_raw_member("clearance_size_y", - clearance_size_y, - "engine.util.placement_mode.type.Place") - - # Allow rotation - place_raw_api_object.add_raw_member("allow_rotation", - True, - "engine.util.placement_mode.type.Place") - - # Max elevation difference - elevation_mode = current_unit["elevation_mode"].value - if elevation_mode == 2: - max_elevation_difference = 0 - - elif elevation_mode == 3: - max_elevation_difference = 1 - - else: - max_elevation_difference = MemberSpecialValue.NYAN_INF - - place_raw_api_object.add_raw_member("max_elevation_difference", - max_elevation_difference, - "engine.util.placement_mode.type.Place") - - line.add_raw_api_object(place_raw_api_object) - - place_forward_ref = ForwardRef(line, obj_name) - placement_modes.append(place_forward_ref) - - else: - placement_modes.append( - dataset.nyan_api_objects["engine.util.placement_mode.type.Eject"]) - - creatable_raw_api_object.add_raw_member("placement_modes", - placement_modes, - "engine.util.create.CreatableGameEntity") - - line.add_raw_api_object(creatable_raw_api_object) - line.add_raw_api_object(cost_raw_api_object) + get_creatable_game_entity = staticmethod(get_creatable_game_entity) From 4d6c1e1ade4b3e3ffcaead1e12f83fbf610c54ee Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 14:42:13 +0200 Subject: [PATCH 136/163] convert: Refactor RoRCivSubprocessor into separate files. --- .../processor/conversion/ror/CMakeLists.txt | 1 + .../conversion/ror/civ/CMakeLists.txt | 4 + .../processor/conversion/ror/civ/__init__.py | 5 + .../conversion/ror/civ/starting_resources.py | 139 ++++++++++++++++++ .../conversion/ror/civ_subprocessor.py | 139 +----------------- 5 files changed, 152 insertions(+), 136 deletions(-) create mode 100644 openage/convert/processor/conversion/ror/civ/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/civ/__init__.py create mode 100644 openage/convert/processor/conversion/ror/civ/starting_resources.py diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index df18000756..17112934fa 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -16,3 +16,4 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(auxiliary) +add_subdirectory(civ) diff --git a/openage/convert/processor/conversion/ror/civ/CMakeLists.txt b/openage/convert/processor/conversion/ror/civ/CMakeLists.txt new file mode 100644 index 0000000000..43533d7f64 --- /dev/null +++ b/openage/convert/processor/conversion/ror/civ/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + starting_resources.py +) diff --git a/openage/convert/processor/conversion/ror/civ/__init__.py b/openage/convert/processor/conversion/ror/civ/__init__.py new file mode 100644 index 0000000000..4c766c2781 --- /dev/null +++ b/openage/convert/processor/conversion/ror/civ/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches and modifiers for civs. +""" diff --git a/openage/convert/processor/conversion/ror/civ/starting_resources.py b/openage/convert/processor/conversion/ror/civ/starting_resources.py new file mode 100644 index 0000000000..2fc76ce15e --- /dev/null +++ b/openage/convert/processor/conversion/ror/civ/starting_resources.py @@ -0,0 +1,139 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ starting resources. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def get_starting_resources(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Returns the starting resources of a civ. + """ + resource_amounts = [] + + civ_id = civ_group.get_id() + dataset = civ_group.data + + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + civ_name = civ_lookup_dict[civ_id][0] + + # Find starting resource amounts + food_amount = civ_group.civ["resources"][0].value + wood_amount = civ_group.civ["resources"][1].value + gold_amount = civ_group.civ["resources"][2].value + stone_amount = civ_group.civ["resources"][3].value + + # Find civ unique starting resources + tech_tree = civ_group.get_tech_tree_effects() + for effect in tech_tree: + type_id = effect.get_type() + + if type_id != 1: + continue + + resource_id = effect["attr_a"].value + amount = effect["attr_d"].value + if resource_id == 0: + food_amount += amount + + elif resource_id == 1: + wood_amount += amount + + elif resource_id == 2: + gold_amount += amount + + elif resource_id == 3: + stone_amount += amount + + food_ref = f"{civ_name}.FoodStartingAmount" + food_raw_api_object = RawAPIObject(food_ref, "FoodStartingAmount", + dataset.nyan_api_objects) + food_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + food_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + food_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + food_raw_api_object.add_raw_member("amount", + food_amount, + "engine.util.resource.ResourceAmount") + + food_forward_ref = ForwardRef(civ_group, food_ref) + resource_amounts.append(food_forward_ref) + + wood_ref = f"{civ_name}.WoodStartingAmount" + wood_raw_api_object = RawAPIObject(wood_ref, "WoodStartingAmount", + dataset.nyan_api_objects) + wood_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + wood_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() + wood_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + wood_raw_api_object.add_raw_member("amount", + wood_amount, + "engine.util.resource.ResourceAmount") + + wood_forward_ref = ForwardRef(civ_group, wood_ref) + resource_amounts.append(wood_forward_ref) + + gold_ref = f"{civ_name}.GoldStartingAmount" + gold_raw_api_object = RawAPIObject(gold_ref, "GoldStartingAmount", + dataset.nyan_api_objects) + gold_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + gold_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() + gold_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + gold_raw_api_object.add_raw_member("amount", + gold_amount, + "engine.util.resource.ResourceAmount") + + gold_forward_ref = ForwardRef(civ_group, gold_ref) + resource_amounts.append(gold_forward_ref) + + stone_ref = f"{civ_name}.StoneStartingAmount" + stone_raw_api_object = RawAPIObject(stone_ref, "StoneStartingAmount", + dataset.nyan_api_objects) + stone_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + stone_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object() + stone_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + stone_raw_api_object.add_raw_member("amount", + stone_amount, + "engine.util.resource.ResourceAmount") + + stone_forward_ref = ForwardRef(civ_group, stone_ref) + resource_amounts.append(stone_forward_ref) + + civ_group.add_raw_api_object(food_raw_api_object) + civ_group.add_raw_api_object(wood_raw_api_object) + civ_group.add_raw_api_object(gold_raw_api_object) + civ_group.add_raw_api_object(stone_raw_api_object) + + return resource_amounts diff --git a/openage/convert/processor/conversion/ror/civ_subprocessor.py b/openage/convert/processor/conversion/ror/civ_subprocessor.py index 0e8f5364ac..ac0d20bc21 100644 --- a/openage/convert/processor/conversion/ror/civ_subprocessor.py +++ b/openage/convert/processor/conversion/ror/civ_subprocessor.py @@ -1,19 +1,9 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-few-public-methods,too-many-statements,too-many-locals +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates patches and modifiers for civs. """ -from __future__ import annotations -import typing - -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup +from .civ.starting_resources import get_starting_resources class RoRCivSubprocessor: @@ -21,127 +11,4 @@ class RoRCivSubprocessor: Creates raw API objects for civs in RoR. """ - @staticmethod - def get_starting_resources(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Returns the starting resources of a civ. - """ - resource_amounts = [] - - civ_id = civ_group.get_id() - dataset = civ_group.data - - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - civ_name = civ_lookup_dict[civ_id][0] - - # Find starting resource amounts - food_amount = civ_group.civ["resources"][0].value - wood_amount = civ_group.civ["resources"][1].value - gold_amount = civ_group.civ["resources"][2].value - stone_amount = civ_group.civ["resources"][3].value - - # Find civ unique starting resources - tech_tree = civ_group.get_tech_tree_effects() - for effect in tech_tree: - type_id = effect.get_type() - - if type_id != 1: - continue - - resource_id = effect["attr_a"].value - amount = effect["attr_d"].value - if resource_id == 0: - food_amount += amount - - elif resource_id == 1: - wood_amount += amount - - elif resource_id == 2: - gold_amount += amount - - elif resource_id == 3: - stone_amount += amount - - food_ref = f"{civ_name}.FoodStartingAmount" - food_raw_api_object = RawAPIObject(food_ref, "FoodStartingAmount", - dataset.nyan_api_objects) - food_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - food_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - food_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - food_raw_api_object.add_raw_member("amount", - food_amount, - "engine.util.resource.ResourceAmount") - - food_forward_ref = ForwardRef(civ_group, food_ref) - resource_amounts.append(food_forward_ref) - - wood_ref = f"{civ_name}.WoodStartingAmount" - wood_raw_api_object = RawAPIObject(wood_ref, "WoodStartingAmount", - dataset.nyan_api_objects) - wood_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - wood_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Wood"].get_nyan_object() - wood_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - wood_raw_api_object.add_raw_member("amount", - wood_amount, - "engine.util.resource.ResourceAmount") - - wood_forward_ref = ForwardRef(civ_group, wood_ref) - resource_amounts.append(wood_forward_ref) - - gold_ref = f"{civ_name}.GoldStartingAmount" - gold_raw_api_object = RawAPIObject(gold_ref, "GoldStartingAmount", - dataset.nyan_api_objects) - gold_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - gold_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Gold"].get_nyan_object() - gold_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - gold_raw_api_object.add_raw_member("amount", - gold_amount, - "engine.util.resource.ResourceAmount") - - gold_forward_ref = ForwardRef(civ_group, gold_ref) - resource_amounts.append(gold_forward_ref) - - stone_ref = f"{civ_name}.StoneStartingAmount" - stone_raw_api_object = RawAPIObject(stone_ref, "StoneStartingAmount", - dataset.nyan_api_objects) - stone_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - stone_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Stone"].get_nyan_object() - stone_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - stone_raw_api_object.add_raw_member("amount", - stone_amount, - "engine.util.resource.ResourceAmount") - - stone_forward_ref = ForwardRef(civ_group, stone_ref) - resource_amounts.append(stone_forward_ref) - - civ_group.add_raw_api_object(food_raw_api_object) - civ_group.add_raw_api_object(wood_raw_api_object) - civ_group.add_raw_api_object(gold_raw_api_object) - civ_group.add_raw_api_object(stone_raw_api_object) - - return resource_amounts + get_starting_resources = staticmethod(get_starting_resources) From 9f2cf4d73121fb52af500309acb1180a03590f57 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 14:59:44 +0200 Subject: [PATCH 137/163] convert: Refactor RoRNyanSubprocessor into separate files. --- .../processor/conversion/ror/CMakeLists.txt | 1 + .../conversion/ror/nyan/CMakeLists.txt | 10 + .../processor/conversion/ror/nyan/__init__.py | 6 + .../processor/conversion/ror/nyan/ambient.py | 102 ++ .../processor/conversion/ror/nyan/building.py | 141 +++ .../processor/conversion/ror/nyan/civ.py | 134 +++ .../conversion/ror/nyan/projectile.py | 79 ++ .../processor/conversion/ror/nyan/tech.py | 132 +++ .../processor/conversion/ror/nyan/terrain.py | 204 ++++ .../processor/conversion/ror/nyan/unit.py | 208 ++++ .../conversion/ror/nyan_subprocessor.py | 953 +----------------- 11 files changed, 1033 insertions(+), 937 deletions(-) create mode 100644 openage/convert/processor/conversion/ror/nyan/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/nyan/__init__.py create mode 100644 openage/convert/processor/conversion/ror/nyan/ambient.py create mode 100644 openage/convert/processor/conversion/ror/nyan/building.py create mode 100644 openage/convert/processor/conversion/ror/nyan/civ.py create mode 100644 openage/convert/processor/conversion/ror/nyan/projectile.py create mode 100644 openage/convert/processor/conversion/ror/nyan/tech.py create mode 100644 openage/convert/processor/conversion/ror/nyan/terrain.py create mode 100644 openage/convert/processor/conversion/ror/nyan/unit.py diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index 17112934fa..4ba3c6bd8e 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -17,3 +17,4 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(auxiliary) add_subdirectory(civ) +add_subdirectory(nyan) diff --git a/openage/convert/processor/conversion/ror/nyan/CMakeLists.txt b/openage/convert/processor/conversion/ror/nyan/CMakeLists.txt new file mode 100644 index 0000000000..78bcb4c0bd --- /dev/null +++ b/openage/convert/processor/conversion/ror/nyan/CMakeLists.txt @@ -0,0 +1,10 @@ +add_py_modules( + __init__.py + ambient.py + building.py + civ.py + projectile.py + tech.py + terrain.py + unit.py +) diff --git a/openage/convert/processor/conversion/ror/nyan/__init__.py b/openage/convert/processor/conversion/ror/nyan/__init__.py new file mode 100644 index 0000000000..37705b3e7b --- /dev/null +++ b/openage/convert/processor/conversion/ror/nyan/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert API-like objects to nyan objects. Subroutine of the +main AoC processor. +""" diff --git a/openage/convert/processor/conversion/ror/nyan/ambient.py b/openage/convert/processor/conversion/ror/nyan/ambient.py new file mode 100644 index 0000000000..2bd1eaa86f --- /dev/null +++ b/openage/convert/processor/conversion/ror/nyan/ambient.py @@ -0,0 +1,102 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert ambient groups to openage game entities. +""" +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + + +def ambient_group_to_game_entity(ambient_group): + """ + Creates raw API objects for an ambient group. + + :param ambient_group: Unit line that gets converted to a game entity. + """ + ambient_unit = ambient_group.get_head_unit() + ambient_id = ambient_group.get_head_unit_id() + + dataset = ambient_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[ambient_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[ambient_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[ambient_id][1]) + ambient_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give an ambient the types + # - util.game_entity_type.types.Ambient + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Ambient"].get_nyan_object( + ) + types_set.append(type_obj) + + unit_class = ambient_unit["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + interaction_mode = ambient_unit["interaction_mode"].value + + if interaction_mode >= 0: + abilities_set.append(AoCAbilitySubprocessor.death_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.resistance_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(ambient_group)) + + if interaction_mode >= 2: + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(ambient_group)) + + if not ambient_group.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(ambient_group)) + + if ambient_group.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(ambient_group)) + + # ======================================================================= + # Abilities + # ======================================================================= + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers_set = [] + + raw_api_object.add_raw_member("modifiers", modifiers_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + variants_set = [] + + raw_api_object.add_raw_member("variants", variants_set, + "engine.util.game_entity.GameEntity") diff --git a/openage/convert/processor/conversion/ror/nyan/building.py b/openage/convert/processor/conversion/ror/nyan/building.py new file mode 100644 index 0000000000..dfa2658bec --- /dev/null +++ b/openage/convert/processor/conversion/ror/nyan/building.py @@ -0,0 +1,141 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert building lines to openage game entities. +""" +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ..ability_subprocessor import RoRAbilitySubprocessor +from ..auxiliary_subprocessor import RoRAuxiliarySubprocessor +from .projectile import projectiles_from_line + + +def building_line_to_game_entity(building_line): + """ + Creates raw API objects for a building line. + + :param building_line: Building line that gets converted to a game entity. + """ + current_building = building_line.line[0] + current_building_id = building_line.get_head_unit_id() + dataset = building_line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[current_building_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[current_building_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[current_building_id][1]) + building_line.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give a building two types + # - util.game_entity_type.types.Building (if unit_type >= 80) + # - util.game_entity_type.types. (depending on the class) + # and additionally + # - util.game_entity_type.types.DropSite (only if this is used as a drop site) + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + unit_type = current_building["unit_type"].value + + if unit_type >= 80: + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( + ) + types_set.append(type_obj) + + unit_class = current_building["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + if building_line.is_dropsite(): + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.DropSite"].get_nyan_object( + ) + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + abilities_set.append(AoCAbilitySubprocessor.attribute_change_tracker_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.death_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) + abilities_set.append(RoRAbilitySubprocessor.resistance_ability(building_line)) + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.stop_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(building_line)) + + # Config abilities + if building_line.is_creatable(): + abilities_set.append(AoCAbilitySubprocessor.constructable_ability(building_line)) + + if building_line.has_foundation(): + abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line)) + + # Creation/Research abilities + if len(building_line.creates) > 0: + abilities_set.append(AoCAbilitySubprocessor.create_ability(building_line)) + abilities_set.append(RoRAbilitySubprocessor.production_queue_ability(building_line)) + + if len(building_line.researches) > 0: + abilities_set.append(AoCAbilitySubprocessor.research_ability(building_line)) + + # Effect abilities + if building_line.is_projectile_shooter(): + abilities_set.append(RoRAbilitySubprocessor.shoot_projectile_ability(building_line, 7)) + abilities_set.append(RoRAbilitySubprocessor.game_entity_stance_ability(building_line)) + projectiles_from_line(building_line) + + # Resource abilities + if building_line.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(building_line)) + + if building_line.is_dropsite(): + abilities_set.append(AoCAbilitySubprocessor.drop_site_ability(building_line)) + + ability = AoCAbilitySubprocessor.provide_contingent_ability(building_line) + if ability: + abilities_set.append(ability) + + # Trade abilities + if building_line.is_trade_post(): + abilities_set.append(AoCAbilitySubprocessor.trade_post_ability(building_line)) + + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + raw_api_object.add_raw_member("modifiers", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Misc (Objects that are not used by the unit line itself, but use its values) + # ======================================================================= + if building_line.is_creatable(): + RoRAuxiliarySubprocessor.get_creatable_game_entity(building_line) diff --git a/openage/convert/processor/conversion/ror/nyan/civ.py b/openage/convert/processor/conversion/ror/nyan/civ.py new file mode 100644 index 0000000000..09f925600f --- /dev/null +++ b/openage/convert/processor/conversion/ror/nyan/civ.py @@ -0,0 +1,134 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert civ groups to openage player setups. +""" +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.civ_subprocessor import AoCCivSubprocessor +from ..civ_subprocessor import RoRCivSubprocessor + + +def civ_group_to_civ(civ_group): + """ + Creates raw API objects for a civ group. + + :param civ_group: Terrain group that gets converted to a tech. + """ + civ_id = civ_group.get_id() + + dataset = civ_group.data + + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + # Start with the Tech object + tech_name = civ_lookup_dict[civ_id][0] + raw_api_object = RawAPIObject(tech_name, tech_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.setup.PlayerSetup") + + obj_location = f"data/civ/{civ_lookup_dict[civ_id][1]}/" + + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(civ_lookup_dict[civ_id][1]) + civ_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{tech_name}.{tech_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{tech_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(civ_group, tech_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(civ_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Description + # ======================================================================= + description_ref = f"{tech_name}.{tech_name}Description" + description_raw_api_object = RawAPIObject(description_ref, + f"{tech_name}Description", + dataset.nyan_api_objects) + description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + description_location = ForwardRef(civ_group, tech_name) + description_raw_api_object.set_location(description_location) + + description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + description_forward_ref = ForwardRef(civ_group, description_ref) + raw_api_object.add_raw_member("description", + description_forward_ref, + "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(description_raw_api_object) + + # ======================================================================= + # Long description + # ======================================================================= + long_description_ref = f"{tech_name}.{tech_name}LongDescription" + long_description_raw_api_object = RawAPIObject(long_description_ref, + f"{tech_name}LongDescription", + dataset.nyan_api_objects) + long_description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + long_description_location = ForwardRef(civ_group, tech_name) + long_description_raw_api_object.set_location(long_description_location) + + long_description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + long_description_forward_ref = ForwardRef(civ_group, long_description_ref) + raw_api_object.add_raw_member("long_description", + long_description_forward_ref, + "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(long_description_raw_api_object) + + # ======================================================================= + # TODO: Leader names + # ======================================================================= + raw_api_object.add_raw_member("leader_names", + [], + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers = [] + # modifiers = AoCCivSubprocessor.get_civ_setup(civ_group) + raw_api_object.add_raw_member("modifiers", + modifiers, + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Starting resources + # ======================================================================= + resource_amounts = RoRCivSubprocessor.get_starting_resources(civ_group) + raw_api_object.add_raw_member("starting_resources", + resource_amounts, + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Game setup + # ======================================================================= + game_setup = AoCCivSubprocessor.get_civ_setup(civ_group) + raw_api_object.add_raw_member("game_setup", + game_setup, + "engine.util.setup.PlayerSetup") diff --git a/openage/convert/processor/conversion/ror/nyan/projectile.py b/openage/convert/processor/conversion/ror/nyan/projectile.py new file mode 100644 index 0000000000..e688702256 --- /dev/null +++ b/openage/convert/processor/conversion/ror/nyan/projectile.py @@ -0,0 +1,79 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert projectiles to openage game entities. +""" +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ..ability_subprocessor import RoRAbilitySubprocessor + + +def projectiles_from_line(line): + """ + Creates Projectile(GameEntity) raw API objects for a unit/building line. + + :param line: Line for which the projectiles are extracted. + """ + current_unit = line.get_head_unit() + 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] + game_entity_filename = name_lookup_dict[current_unit_id][1] + + projectiles_location = f"data/game_entity/generic/{game_entity_filename}/projectiles/" + + projectile_indices = [] + projectile_primary = current_unit["projectile_id0"].value + if projectile_primary > -1: + projectile_indices.append(0) + + for projectile_num in projectile_indices: + obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{projectile_num}" + obj_name = f"Projectile{projectile_num}" + proj_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) + proj_raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + proj_raw_api_object.set_location(projectiles_location) + proj_raw_api_object.set_filename(f"{game_entity_filename}_projectiles") + + # ======================================================================= + # Types + # ======================================================================= + types_set = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Projectile"].get_nyan_object()] + proj_raw_api_object.add_raw_member( + "types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + abilities_set.append(RoRAbilitySubprocessor.projectile_ability( + line, position=projectile_num)) + abilities_set.append(AoCAbilitySubprocessor.move_projectile_ability( + line, position=projectile_num)) + abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( + line, 7, False, projectile_num)) + # TODO: Death, Despawn + proj_raw_api_object.add_raw_member( + "abilities", abilities_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers_set = [] + + proj_raw_api_object.add_raw_member( + "modifiers", modifiers_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Variants + # ======================================================================= + variants_set = [] + proj_raw_api_object.add_raw_member( + "variants", variants_set, "engine.util.game_entity.GameEntity") + + line.add_raw_api_object(proj_raw_api_object) diff --git a/openage/convert/processor/conversion/ror/nyan/tech.py b/openage/convert/processor/conversion/ror/nyan/tech.py new file mode 100644 index 0000000000..62d5fe61c9 --- /dev/null +++ b/openage/convert/processor/conversion/ror/nyan/tech.py @@ -0,0 +1,132 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert tech groups to openage techs. +""" +from .....entity_object.conversion.converter_object import RawAPIObject +from .....entity_object.conversion.ror.genie_tech import RoRUnitLineUpgrade +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor +from ..tech_subprocessor import RoRTechSubprocessor + + +def tech_group_to_tech(tech_group): + """ + Creates raw API objects for a tech group. + + :param tech_group: Tech group that gets converted to a tech. + """ + tech_id = tech_group.get_id() + + # Skip Dark Age tech + if tech_id == 104: + return + + dataset = tech_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + # Start with the Tech object + tech_name = tech_lookup_dict[tech_id][0] + raw_api_object = RawAPIObject(tech_name, tech_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.tech.Tech") + + if isinstance(tech_group, RoRUnitLineUpgrade): + unit_line = dataset.unit_lines[tech_group.get_line_id()] + head_unit_id = unit_line.get_head_unit_id() + obj_location = f"data/game_entity/generic/{name_lookup_dict[head_unit_id][1]}/" + + else: + obj_location = f"data/tech/generic/{tech_lookup_dict[tech_id][1]}/" + + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(tech_lookup_dict[tech_id][1]) + tech_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Types + # ======================================================================= + raw_api_object.add_raw_member("types", [], "engine.util.tech.Tech") + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{tech_name}.{tech_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{tech_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(tech_group, tech_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(tech_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.tech.Tech") + tech_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Description + # ======================================================================= + description_ref = f"{tech_name}.{tech_name}Description" + description_raw_api_object = RawAPIObject(description_ref, + f"{tech_name}Description", + dataset.nyan_api_objects) + description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + description_location = ForwardRef(tech_group, tech_name) + description_raw_api_object.set_location(description_location) + + description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + description_forward_ref = ForwardRef(tech_group, description_ref) + raw_api_object.add_raw_member("description", + description_forward_ref, + "engine.util.tech.Tech") + tech_group.add_raw_api_object(description_raw_api_object) + + # ======================================================================= + # Long description + # ======================================================================= + long_description_ref = f"{tech_name}.{tech_name}LongDescription" + long_description_raw_api_object = RawAPIObject(long_description_ref, + f"{tech_name}LongDescription", + dataset.nyan_api_objects) + long_description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + long_description_location = ForwardRef(tech_group, tech_name) + long_description_raw_api_object.set_location(long_description_location) + + long_description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + long_description_forward_ref = ForwardRef(tech_group, long_description_ref) + raw_api_object.add_raw_member("long_description", + long_description_forward_ref, + "engine.util.tech.Tech") + tech_group.add_raw_api_object(long_description_raw_api_object) + + # ======================================================================= + # Updates + # ======================================================================= + patches = [] + patches.extend(RoRTechSubprocessor.get_patches(tech_group)) + raw_api_object.add_raw_member("updates", patches, "engine.util.tech.Tech") + + # ======================================================================= + # Misc (Objects that are not used by the tech group itself, but use its values) + # ======================================================================= + if tech_group.is_researchable(): + AoCAuxiliarySubprocessor.get_researchable_tech(tech_group) diff --git a/openage/convert/processor/conversion/ror/nyan/terrain.py b/openage/convert/processor/conversion/ror/nyan/terrain.py new file mode 100644 index 0000000000..9f1c4a5faa --- /dev/null +++ b/openage/convert/processor/conversion/ror/nyan/terrain.py @@ -0,0 +1,204 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert terrain groups to openage terrains. +""" +from .....entity_object.conversion.combined_terrain import CombinedTerrain +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + + +def terrain_group_to_terrain(terrain_group): + """ + Creates raw API objects for a terrain group. + + :param terrain_group: Terrain group that gets converted to a tech. + """ + terrain_index = terrain_group.get_id() + + dataset = terrain_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) + terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( + dataset.game_version) + + # Start with the Terrain object + terrain_name = terrain_lookup_dict[terrain_index][1] + raw_api_object = RawAPIObject(terrain_name, terrain_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.terrain.Terrain") + obj_location = f"data/terrain/{terrain_lookup_dict[terrain_index][2]}/" + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(terrain_lookup_dict[terrain_index][2]) + terrain_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Types + # ======================================================================= + terrain_types = [] + + for terrain_type in terrain_type_lookup_dict.values(): + if terrain_index in terrain_type[0]: + type_name = f"util.terrain_type.types.{terrain_type[2]}" + type_obj = dataset.pregen_nyan_objects[type_name].get_nyan_object() + terrain_types.append(type_obj) + + raw_api_object.add_raw_member("types", terrain_types, "engine.util.terrain.Terrain") + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{terrain_name}.{terrain_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{terrain_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(terrain_group, terrain_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(terrain_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.terrain.Terrain") + terrain_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Sound + # ======================================================================= + sound_name = f"{terrain_name}.Sound" + sound_raw_api_object = RawAPIObject(sound_name, "Sound", + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(terrain_group, terrain_name) + sound_raw_api_object.set_location(sound_location) + + # Sounds for terrains don't exist in AoC + sounds = [] + + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + sounds, + "engine.util.sound.Sound") + + sound_forward_ref = ForwardRef(terrain_group, sound_name) + raw_api_object.add_raw_member("sound", + sound_forward_ref, + "engine.util.terrain.Terrain") + + terrain_group.add_raw_api_object(sound_raw_api_object) + + # ======================================================================= + # Ambience + # ======================================================================= + terrain = terrain_group.get_terrain() + ambients_count = terrain["terrain_units_used_count"].value + + ambience = [] + for ambient_index in range(ambients_count): + ambient_id = terrain["terrain_unit_id"][ambient_index].value + + if ambient_id not in dataset.unit_ref.keys(): + # Unit does not exist + continue + + ambient_line = dataset.unit_ref[ambient_id] + ambient_name = name_lookup_dict[ambient_line.get_head_unit_id()][0] + + ambient_ref = f"{terrain_name}.Ambient{str(ambient_index)}" + ambient_raw_api_object = RawAPIObject(ambient_ref, + f"Ambient{str(ambient_index)}", + dataset.nyan_api_objects) + ambient_raw_api_object.add_raw_parent("engine.util.terrain.TerrainAmbient") + ambient_location = ForwardRef(terrain_group, terrain_name) + ambient_raw_api_object.set_location(ambient_location) + + # Game entity reference + ambient_line_forward_ref = ForwardRef(ambient_line, ambient_name) + ambient_raw_api_object.add_raw_member("object", + ambient_line_forward_ref, + "engine.util.terrain.TerrainAmbient") + + # Max density + max_density = terrain["terrain_unit_density"][ambient_index].value + ambient_raw_api_object.add_raw_member("max_density", + max_density, + "engine.util.terrain.TerrainAmbient") + + terrain_group.add_raw_api_object(ambient_raw_api_object) + terrain_ambient_forward_ref = ForwardRef(terrain_group, ambient_ref) + ambience.append(terrain_ambient_forward_ref) + + raw_api_object.add_raw_member("ambience", ambience, "engine.util.terrain.Terrain") + + # ======================================================================= + # Path Costs + # ======================================================================= + path_costs = {} + restrictions = dataset.genie_terrain_restrictions + + # Land grid + path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() + land_restrictions = restrictions[0x07] + if land_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Water grid + path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() + water_restrictions = restrictions[0x03] + if water_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Air grid (default accessible) + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + path_costs[path_type] = 1 + + raw_api_object.add_raw_member("path_costs", path_costs, "engine.util.terrain.Terrain") + + # ======================================================================= + # Graphic + # ======================================================================= + if terrain_group.has_subterrain(): + subterrain = terrain_group.get_subterrain() + terrain_id = subterrain.get_id() + + else: + terrain_id = terrain_group.get_id() + + # Create animation object + graphic_name = f"{terrain_name}.TerrainTexture" + graphic_raw_api_object = RawAPIObject(graphic_name, "TerrainTexture", + dataset.nyan_api_objects) + graphic_raw_api_object.add_raw_parent("engine.util.graphics.Terrain") + graphic_location = ForwardRef(terrain_group, terrain_name) + graphic_raw_api_object.set_location(graphic_location) + + if terrain_id in dataset.combined_terrains.keys(): + terrain_graphic = dataset.combined_terrains[terrain_id] + + else: + terrain_graphic = CombinedTerrain(terrain_id, + f"texture_{terrain_lookup_dict[terrain_index][2]}", + dataset) + dataset.combined_terrains.update({terrain_graphic.get_id(): terrain_graphic}) + + terrain_graphic.add_reference(graphic_raw_api_object) + + graphic_raw_api_object.add_raw_member("sprite", terrain_graphic, + "engine.util.graphics.Terrain") + + terrain_group.add_raw_api_object(graphic_raw_api_object) + graphic_forward_ref = ForwardRef(terrain_group, graphic_name) + raw_api_object.add_raw_member("terrain_graphic", graphic_forward_ref, + "engine.util.terrain.Terrain") diff --git a/openage/convert/processor/conversion/ror/nyan/unit.py b/openage/convert/processor/conversion/ror/nyan/unit.py new file mode 100644 index 0000000000..a73276ad04 --- /dev/null +++ b/openage/convert/processor/conversion/ror/nyan/unit.py @@ -0,0 +1,208 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert unit lines to openage game entities. +""" +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ...aoc.modifier_subprocessor import AoCModifierSubprocessor +from ..ability_subprocessor import RoRAbilitySubprocessor +from ..auxiliary_subprocessor import RoRAuxiliarySubprocessor +from .projectile import projectiles_from_line + + +def unit_line_to_game_entity(unit_line): + """ + Creates raw API objects for a unit line. + + :param unit_line: Unit line that gets converted to a game entity. + """ + current_unit = unit_line.get_head_unit() + current_unit_id = unit_line.get_head_unit_id() + + dataset = unit_line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[current_unit_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[current_unit_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[current_unit_id][1]) + unit_line.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give a unit two types + # - util.game_entity_type.types.Unit (if unit_type >= 70) + # - util.game_entity_type.types. (depending on the class) + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + unit_type = current_unit["unit_type"].value + + if unit_type >= 70: + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object( + ) + types_set.append(type_obj) + + unit_class = current_unit["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.los_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.move_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(unit_line)) + abilities_set.append(RoRAbilitySubprocessor.resistance_ability(unit_line)) + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.stop_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.turn_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(unit_line)) + + # Creation + if len(unit_line.creates) > 0: + abilities_set.append(AoCAbilitySubprocessor.create_ability(unit_line)) + + # Config + ability = AoCAbilitySubprocessor.use_contingent_ability(unit_line) + if ability: + abilities_set.append(ability) + + if unit_line.has_command(104): + # Recharging attribute points (priests) + abilities_set.extend(AoCAbilitySubprocessor.regenerate_attribute_ability(unit_line)) + + # Applying effects and shooting projectiles + if unit_line.is_projectile_shooter(): + abilities_set.append(RoRAbilitySubprocessor.shoot_projectile_ability(unit_line, 7)) + projectiles_from_line(unit_line) + + elif unit_line.is_melee() or unit_line.is_ranged(): + if unit_line.has_command(7): + # Attack + abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( + unit_line, + 7, + unit_line.is_ranged()) + ) + + if unit_line.has_command(101): + # Build + abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 101, + unit_line.is_ranged()) + ) + + if unit_line.has_command(104): + # TODO: Success chance is not a resource in RoR + # Convert + abilities_set.append(RoRAbilitySubprocessor.apply_discrete_effect_ability( + unit_line, + 104, + unit_line.is_ranged()) + ) + + if unit_line.has_command(105): + # Heal + abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 105, + unit_line.is_ranged()) + ) + + if unit_line.has_command(106): + # Repair + abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 106, + unit_line.is_ranged()) + ) + + # Formation/Stance + if not isinstance(unit_line, GenieVillagerGroup): + abilities_set.append(RoRAbilitySubprocessor.game_entity_stance_ability(unit_line)) + + # Storage abilities + if unit_line.is_garrison(): + abilities_set.append(AoCAbilitySubprocessor.storage_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(unit_line)) + + if len(unit_line.garrison_locations) > 0: + ability = AoCAbilitySubprocessor.enter_container_ability(unit_line) + if ability: + abilities_set.append(ability) + + ability = AoCAbilitySubprocessor.exit_container_ability(unit_line) + if ability: + abilities_set.append(ability) + + # Resource abilities + if unit_line.is_gatherer(): + abilities_set.append(AoCAbilitySubprocessor.drop_resources_ability(unit_line)) + abilities_set.extend(AoCAbilitySubprocessor.gather_ability(unit_line)) + + # Resource storage + if unit_line.is_gatherer() or unit_line.has_command(111): + abilities_set.append(AoCAbilitySubprocessor.resource_storage_ability(unit_line)) + + if unit_line.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(unit_line)) + + # Trade abilities + if unit_line.has_command(111): + abilities_set.append(AoCAbilitySubprocessor.trade_ability(unit_line)) + + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers_set = [] + + if unit_line.is_gatherer(): + modifiers_set.extend(AoCModifierSubprocessor.gather_rate_modifier(unit_line)) + + # TODO: Other modifiers? + + raw_api_object.add_raw_member("modifiers", modifiers_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + variants_set = [] + + raw_api_object.add_raw_member("variants", variants_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Misc (Objects that are not used by the unit line itself, but use its values) + # ======================================================================= + if unit_line.is_creatable(): + RoRAuxiliarySubprocessor.get_creatable_game_entity(unit_line) diff --git a/openage/convert/processor/conversion/ror/nyan_subprocessor.py b/openage/convert/processor/conversion/ror/nyan_subprocessor.py index c3f6b09706..0316c46853 100644 --- a/openage/convert/processor/conversion/ror/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/ror/nyan_subprocessor.py @@ -1,29 +1,18 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Convert API-like objects to nyan objects. Subroutine of the main RoR processor. Reuses functionality from the AoC subprocessor. """ -from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup -from ....entity_object.conversion.combined_terrain import CombinedTerrain -from ....entity_object.conversion.converter_object import RawAPIObject -from ....entity_object.conversion.ror.genie_tech import RoRUnitLineUpgrade -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ..aoc.ability_subprocessor import AoCAbilitySubprocessor -from ..aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor -from ..aoc.civ_subprocessor import AoCCivSubprocessor -from ..aoc.modifier_subprocessor import AoCModifierSubprocessor from ..aoc.nyan_subprocessor import AoCNyanSubprocessor -from .ability_subprocessor import RoRAbilitySubprocessor -from .auxiliary_subprocessor import RoRAuxiliarySubprocessor -from .civ_subprocessor import RoRCivSubprocessor -from .tech_subprocessor import RoRTechSubprocessor + +from .nyan.ambient import ambient_group_to_game_entity +from .nyan.building import building_line_to_game_entity +from .nyan.civ import civ_group_to_civ +from .nyan.projectile import projectiles_from_line +from .nyan.tech import tech_group_to_tech +from .nyan.terrain import terrain_group_to_terrain +from .nyan.unit import unit_line_to_game_entity class RoRNyanSubprocessor: @@ -154,920 +143,10 @@ def _process_game_entities(cls, full_data_set): for civ_group in full_data_set.civ_groups.values(): cls.civ_group_to_civ(civ_group) - @staticmethod - def unit_line_to_game_entity(unit_line): - """ - Creates raw API objects for a unit line. - - :param unit_line: Unit line that gets converted to a game entity. - :type unit_line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_unit = unit_line.get_head_unit() - current_unit_id = unit_line.get_head_unit_id() - - dataset = unit_line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[current_unit_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[current_unit_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[current_unit_id][1]) - unit_line.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give a unit two types - # - util.game_entity_type.types.Unit (if unit_type >= 70) - # - util.game_entity_type.types. (depending on the class) - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - unit_type = current_unit["unit_type"].value - - if unit_type >= 70: - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = current_unit["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.los_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.move_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(unit_line)) - abilities_set.append(RoRAbilitySubprocessor.resistance_ability(unit_line)) - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.stop_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.turn_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(unit_line)) - - # Creation - if len(unit_line.creates) > 0: - abilities_set.append(AoCAbilitySubprocessor.create_ability(unit_line)) - - # Config - ability = AoCAbilitySubprocessor.use_contingent_ability(unit_line) - if ability: - abilities_set.append(ability) - - if unit_line.has_command(104): - # Recharging attribute points (priests) - abilities_set.extend(AoCAbilitySubprocessor.regenerate_attribute_ability(unit_line)) - - # Applying effects and shooting projectiles - if unit_line.is_projectile_shooter(): - abilities_set.append(RoRAbilitySubprocessor.shoot_projectile_ability(unit_line, 7)) - RoRNyanSubprocessor.projectiles_from_line(unit_line) - - elif unit_line.is_melee() or unit_line.is_ranged(): - if unit_line.has_command(7): - # Attack - abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability(unit_line, - 7, - unit_line.is_ranged())) - - if unit_line.has_command(101): - # Build - abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 101, - unit_line.is_ranged())) - - if unit_line.has_command(104): - # TODO: Success chance is not a resource in RoR - # Convert - abilities_set.append(RoRAbilitySubprocessor.apply_discrete_effect_ability(unit_line, - 104, - unit_line.is_ranged())) - - if unit_line.has_command(105): - # Heal - abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 105, - unit_line.is_ranged())) - - if unit_line.has_command(106): - # Repair - abilities_set.append(AoCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 106, - unit_line.is_ranged())) - - # Formation/Stance - if not isinstance(unit_line, GenieVillagerGroup): - abilities_set.append(RoRAbilitySubprocessor.game_entity_stance_ability(unit_line)) - - # Storage abilities - if unit_line.is_garrison(): - abilities_set.append(AoCAbilitySubprocessor.storage_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(unit_line)) - - if len(unit_line.garrison_locations) > 0: - ability = AoCAbilitySubprocessor.enter_container_ability(unit_line) - if ability: - abilities_set.append(ability) - - ability = AoCAbilitySubprocessor.exit_container_ability(unit_line) - if ability: - abilities_set.append(ability) - - # Resource abilities - if unit_line.is_gatherer(): - abilities_set.append(AoCAbilitySubprocessor.drop_resources_ability(unit_line)) - abilities_set.extend(AoCAbilitySubprocessor.gather_ability(unit_line)) - - # Resource storage - if unit_line.is_gatherer() or unit_line.has_command(111): - abilities_set.append(AoCAbilitySubprocessor.resource_storage_ability(unit_line)) - - if unit_line.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(unit_line)) - - # Trade abilities - if unit_line.has_command(111): - abilities_set.append(AoCAbilitySubprocessor.trade_ability(unit_line)) - - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers_set = [] - - if unit_line.is_gatherer(): - modifiers_set.extend(AoCModifierSubprocessor.gather_rate_modifier(unit_line)) - - # TODO: Other modifiers? - - raw_api_object.add_raw_member("modifiers", modifiers_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - variants_set = [] - - raw_api_object.add_raw_member("variants", variants_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Misc (Objects that are not used by the unit line itself, but use its values) - # ======================================================================= - if unit_line.is_creatable(): - RoRAuxiliarySubprocessor.get_creatable_game_entity(unit_line) - - @staticmethod - def building_line_to_game_entity(building_line): - """ - Creates raw API objects for a building line. - - :param building_line: Building line that gets converted to a game entity. - :type building_line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_building = building_line.line[0] - current_building_id = building_line.get_head_unit_id() - dataset = building_line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[current_building_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[current_building_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[current_building_id][1]) - building_line.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give a building two types - # - util.game_entity_type.types.Building (if unit_type >= 80) - # - util.game_entity_type.types. (depending on the class) - # and additionally - # - util.game_entity_type.types.DropSite (only if this is used as a drop site) - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - unit_type = current_building["unit_type"].value - - if unit_type >= 80: - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = current_building["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - if building_line.is_dropsite(): - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.DropSite"].get_nyan_object( - ) - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - abilities_set.append(AoCAbilitySubprocessor.attribute_change_tracker_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.death_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) - abilities_set.append(RoRAbilitySubprocessor.resistance_ability(building_line)) - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.stop_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(building_line)) - - # Config abilities - if building_line.is_creatable(): - abilities_set.append(AoCAbilitySubprocessor.constructable_ability(building_line)) - - if building_line.has_foundation(): - abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line)) - - # Creation/Research abilities - if len(building_line.creates) > 0: - abilities_set.append(AoCAbilitySubprocessor.create_ability(building_line)) - abilities_set.append(RoRAbilitySubprocessor.production_queue_ability(building_line)) - - if len(building_line.researches) > 0: - abilities_set.append(AoCAbilitySubprocessor.research_ability(building_line)) - - # Effect abilities - if building_line.is_projectile_shooter(): - abilities_set.append(RoRAbilitySubprocessor.shoot_projectile_ability(building_line, 7)) - abilities_set.append(RoRAbilitySubprocessor.game_entity_stance_ability(building_line)) - RoRNyanSubprocessor.projectiles_from_line(building_line) - - # Resource abilities - if building_line.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(building_line)) - - if building_line.is_dropsite(): - abilities_set.append(AoCAbilitySubprocessor.drop_site_ability(building_line)) - - ability = AoCAbilitySubprocessor.provide_contingent_ability(building_line) - if ability: - abilities_set.append(ability) - - # Trade abilities - if building_line.is_trade_post(): - abilities_set.append(AoCAbilitySubprocessor.trade_post_ability(building_line)) - - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - raw_api_object.add_raw_member("modifiers", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Misc (Objects that are not used by the unit line itself, but use its values) - # ======================================================================= - if building_line.is_creatable(): - RoRAuxiliarySubprocessor.get_creatable_game_entity(building_line) - - @staticmethod - def ambient_group_to_game_entity(ambient_group): - """ - Creates raw API objects for an ambient group. - - :param ambient_group: Unit line that gets converted to a game entity. - :type ambient_group: ..dataformat.converter_object.ConverterObjectGroup - """ - ambient_unit = ambient_group.get_head_unit() - ambient_id = ambient_group.get_head_unit_id() - - dataset = ambient_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[ambient_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[ambient_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[ambient_id][1]) - ambient_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give an ambient the types - # - util.game_entity_type.types.Ambient - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Ambient"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = ambient_unit["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - interaction_mode = ambient_unit["interaction_mode"].value - - if interaction_mode >= 0: - abilities_set.append(AoCAbilitySubprocessor.death_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.resistance_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(ambient_group)) - - if interaction_mode >= 2: - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(ambient_group)) - - if not ambient_group.is_passable(): - abilities_set.append(AoCAbilitySubprocessor.pathable_ability(ambient_group)) - - if ambient_group.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(ambient_group)) - - # ======================================================================= - # Abilities - # ======================================================================= - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers_set = [] - - raw_api_object.add_raw_member("modifiers", modifiers_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - variants_set = [] - - raw_api_object.add_raw_member("variants", variants_set, - "engine.util.game_entity.GameEntity") - - @staticmethod - def tech_group_to_tech(tech_group): - """ - Creates raw API objects for a tech group. - - :param tech_group: Tech group that gets converted to a tech. - :type tech_group: ..dataformat.converter_object.ConverterObjectGroup - """ - tech_id = tech_group.get_id() - - # Skip Dark Age tech - if tech_id == 104: - return - - dataset = tech_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - # Start with the Tech object - tech_name = tech_lookup_dict[tech_id][0] - raw_api_object = RawAPIObject(tech_name, tech_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.tech.Tech") - - if isinstance(tech_group, RoRUnitLineUpgrade): - unit_line = dataset.unit_lines[tech_group.get_line_id()] - head_unit_id = unit_line.get_head_unit_id() - obj_location = f"data/game_entity/generic/{name_lookup_dict[head_unit_id][1]}/" - - else: - obj_location = f"data/tech/generic/{tech_lookup_dict[tech_id][1]}/" - - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(tech_lookup_dict[tech_id][1]) - tech_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Types - # ======================================================================= - raw_api_object.add_raw_member("types", [], "engine.util.tech.Tech") - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{tech_name}.{tech_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{tech_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(tech_group, tech_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(tech_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.tech.Tech") - tech_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Description - # ======================================================================= - description_ref = f"{tech_name}.{tech_name}Description" - description_raw_api_object = RawAPIObject(description_ref, - f"{tech_name}Description", - dataset.nyan_api_objects) - description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - description_location = ForwardRef(tech_group, tech_name) - description_raw_api_object.set_location(description_location) - - description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - description_forward_ref = ForwardRef(tech_group, description_ref) - raw_api_object.add_raw_member("description", - description_forward_ref, - "engine.util.tech.Tech") - tech_group.add_raw_api_object(description_raw_api_object) - - # ======================================================================= - # Long description - # ======================================================================= - long_description_ref = f"{tech_name}.{tech_name}LongDescription" - long_description_raw_api_object = RawAPIObject(long_description_ref, - f"{tech_name}LongDescription", - dataset.nyan_api_objects) - long_description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - long_description_location = ForwardRef(tech_group, tech_name) - long_description_raw_api_object.set_location(long_description_location) - - long_description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - long_description_forward_ref = ForwardRef(tech_group, long_description_ref) - raw_api_object.add_raw_member("long_description", - long_description_forward_ref, - "engine.util.tech.Tech") - tech_group.add_raw_api_object(long_description_raw_api_object) - - # ======================================================================= - # Updates - # ======================================================================= - patches = [] - patches.extend(RoRTechSubprocessor.get_patches(tech_group)) - raw_api_object.add_raw_member("updates", patches, "engine.util.tech.Tech") - - # ======================================================================= - # Misc (Objects that are not used by the tech group itself, but use its values) - # ======================================================================= - if tech_group.is_researchable(): - AoCAuxiliarySubprocessor.get_researchable_tech(tech_group) - - @staticmethod - def terrain_group_to_terrain(terrain_group): - """ - Creates raw API objects for a terrain group. - - :param terrain_group: Terrain group that gets converted to a tech. - :type terrain_group: ..dataformat.converter_object.ConverterObjectGroup - """ - terrain_index = terrain_group.get_id() - - dataset = terrain_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - terrain_lookup_dict = internal_name_lookups.get_terrain_lookups(dataset.game_version) - terrain_type_lookup_dict = internal_name_lookups.get_terrain_type_lookups( - dataset.game_version) - - # Start with the Terrain object - terrain_name = terrain_lookup_dict[terrain_index][1] - raw_api_object = RawAPIObject(terrain_name, terrain_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.terrain.Terrain") - obj_location = f"data/terrain/{terrain_lookup_dict[terrain_index][2]}/" - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(terrain_lookup_dict[terrain_index][2]) - terrain_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Types - # ======================================================================= - terrain_types = [] - - for terrain_type in terrain_type_lookup_dict.values(): - if terrain_index in terrain_type[0]: - type_name = f"util.terrain_type.types.{terrain_type[2]}" - type_obj = dataset.pregen_nyan_objects[type_name].get_nyan_object() - terrain_types.append(type_obj) - - raw_api_object.add_raw_member("types", terrain_types, "engine.util.terrain.Terrain") - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{terrain_name}.{terrain_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{terrain_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(terrain_group, terrain_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(terrain_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.terrain.Terrain") - terrain_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Sound - # ======================================================================= - sound_name = f"{terrain_name}.Sound" - sound_raw_api_object = RawAPIObject(sound_name, "Sound", - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(terrain_group, terrain_name) - sound_raw_api_object.set_location(sound_location) - - # Sounds for terrains don't exist in AoC - sounds = [] - - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - sounds, - "engine.util.sound.Sound") - - sound_forward_ref = ForwardRef(terrain_group, sound_name) - raw_api_object.add_raw_member("sound", - sound_forward_ref, - "engine.util.terrain.Terrain") - - terrain_group.add_raw_api_object(sound_raw_api_object) - - # ======================================================================= - # Ambience - # ======================================================================= - terrain = terrain_group.get_terrain() - ambients_count = terrain["terrain_units_used_count"].value - - ambience = [] - for ambient_index in range(ambients_count): - ambient_id = terrain["terrain_unit_id"][ambient_index].value - - if ambient_id not in dataset.unit_ref.keys(): - # Unit does not exist - continue - - ambient_line = dataset.unit_ref[ambient_id] - ambient_name = name_lookup_dict[ambient_line.get_head_unit_id()][0] - - ambient_ref = f"{terrain_name}.Ambient{str(ambient_index)}" - ambient_raw_api_object = RawAPIObject(ambient_ref, - f"Ambient{str(ambient_index)}", - dataset.nyan_api_objects) - ambient_raw_api_object.add_raw_parent("engine.util.terrain.TerrainAmbient") - ambient_location = ForwardRef(terrain_group, terrain_name) - ambient_raw_api_object.set_location(ambient_location) - - # Game entity reference - ambient_line_forward_ref = ForwardRef(ambient_line, ambient_name) - ambient_raw_api_object.add_raw_member("object", - ambient_line_forward_ref, - "engine.util.terrain.TerrainAmbient") - - # Max density - max_density = terrain["terrain_unit_density"][ambient_index].value - ambient_raw_api_object.add_raw_member("max_density", - max_density, - "engine.util.terrain.TerrainAmbient") - - terrain_group.add_raw_api_object(ambient_raw_api_object) - terrain_ambient_forward_ref = ForwardRef(terrain_group, ambient_ref) - ambience.append(terrain_ambient_forward_ref) - - raw_api_object.add_raw_member("ambience", ambience, "engine.util.terrain.Terrain") - - # ======================================================================= - # Path Costs - # ======================================================================= - path_costs = {} - restrictions = dataset.genie_terrain_restrictions - - # Land grid - path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() - land_restrictions = restrictions[0x07] - if land_restrictions.is_accessible(terrain_index): - path_costs[path_type] = 1 - - else: - path_costs[path_type] = 255 - - # Water grid - path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() - water_restrictions = restrictions[0x03] - if water_restrictions.is_accessible(terrain_index): - path_costs[path_type] = 1 - - else: - path_costs[path_type] = 255 - - # Air grid (default accessible) - path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() - path_costs[path_type] = 1 - - raw_api_object.add_raw_member("path_costs", path_costs, "engine.util.terrain.Terrain") - - # ======================================================================= - # Graphic - # ======================================================================= - if terrain_group.has_subterrain(): - subterrain = terrain_group.get_subterrain() - terrain_id = subterrain.get_id() - - else: - terrain_id = terrain_group.get_id() - - # Create animation object - graphic_name = f"{terrain_name}.TerrainTexture" - graphic_raw_api_object = RawAPIObject(graphic_name, "TerrainTexture", - dataset.nyan_api_objects) - graphic_raw_api_object.add_raw_parent("engine.util.graphics.Terrain") - graphic_location = ForwardRef(terrain_group, terrain_name) - graphic_raw_api_object.set_location(graphic_location) - - if terrain_id in dataset.combined_terrains.keys(): - terrain_graphic = dataset.combined_terrains[terrain_id] - - else: - terrain_graphic = CombinedTerrain(terrain_id, - f"texture_{terrain_lookup_dict[terrain_index][2]}", - dataset) - dataset.combined_terrains.update({terrain_graphic.get_id(): terrain_graphic}) - - terrain_graphic.add_reference(graphic_raw_api_object) - - graphic_raw_api_object.add_raw_member("sprite", terrain_graphic, - "engine.util.graphics.Terrain") - - terrain_group.add_raw_api_object(graphic_raw_api_object) - graphic_forward_ref = ForwardRef(terrain_group, graphic_name) - raw_api_object.add_raw_member("terrain_graphic", graphic_forward_ref, - "engine.util.terrain.Terrain") - - @staticmethod - def civ_group_to_civ(civ_group): - """ - Creates raw API objects for a civ group. - - :param civ_group: Terrain group that gets converted to a tech. - :type civ_group: ..dataformat.converter_object.ConverterObjectGroup - """ - civ_id = civ_group.get_id() - - dataset = civ_group.data - - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - # Start with the Tech object - tech_name = civ_lookup_dict[civ_id][0] - raw_api_object = RawAPIObject(tech_name, tech_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.setup.PlayerSetup") - - obj_location = f"data/civ/{civ_lookup_dict[civ_id][1]}/" - - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(civ_lookup_dict[civ_id][1]) - civ_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{tech_name}.{tech_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{tech_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(civ_group, tech_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(civ_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Description - # ======================================================================= - description_ref = f"{tech_name}.{tech_name}Description" - description_raw_api_object = RawAPIObject(description_ref, - f"{tech_name}Description", - dataset.nyan_api_objects) - description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - description_location = ForwardRef(civ_group, tech_name) - description_raw_api_object.set_location(description_location) - - description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - description_forward_ref = ForwardRef(civ_group, description_ref) - raw_api_object.add_raw_member("description", - description_forward_ref, - "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(description_raw_api_object) - - # ======================================================================= - # Long description - # ======================================================================= - long_description_ref = f"{tech_name}.{tech_name}LongDescription" - long_description_raw_api_object = RawAPIObject(long_description_ref, - f"{tech_name}LongDescription", - dataset.nyan_api_objects) - long_description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - long_description_location = ForwardRef(civ_group, tech_name) - long_description_raw_api_object.set_location(long_description_location) - - long_description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - long_description_forward_ref = ForwardRef(civ_group, long_description_ref) - raw_api_object.add_raw_member("long_description", - long_description_forward_ref, - "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(long_description_raw_api_object) - - # ======================================================================= - # TODO: Leader names - # ======================================================================= - raw_api_object.add_raw_member("leader_names", - [], - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers = [] - # modifiers = AoCCivSubprocessor.get_civ_setup(civ_group) - raw_api_object.add_raw_member("modifiers", - modifiers, - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Starting resources - # ======================================================================= - resource_amounts = RoRCivSubprocessor.get_starting_resources(civ_group) - raw_api_object.add_raw_member("starting_resources", - resource_amounts, - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Game setup - # ======================================================================= - game_setup = AoCCivSubprocessor.get_civ_setup(civ_group) - raw_api_object.add_raw_member("game_setup", - game_setup, - "engine.util.setup.PlayerSetup") - - @staticmethod - def projectiles_from_line(line): - """ - Creates Projectile(GameEntity) raw API objects for a unit/building line. - - :param line: Line for which the projectiles are extracted. - :type line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_unit = line.get_head_unit() - 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] - game_entity_filename = name_lookup_dict[current_unit_id][1] - - projectiles_location = f"data/game_entity/generic/{game_entity_filename}/projectiles/" - - projectile_indices = [] - projectile_primary = current_unit["projectile_id0"].value - if projectile_primary > -1: - projectile_indices.append(0) - - for projectile_num in projectile_indices: - obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{projectile_num}" - obj_name = f"Projectile{projectile_num}" - proj_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) - proj_raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - proj_raw_api_object.set_location(projectiles_location) - proj_raw_api_object.set_filename(f"{game_entity_filename}_projectiles") - - # ======================================================================= - # Types - # ======================================================================= - types_set = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Projectile"].get_nyan_object()] - proj_raw_api_object.add_raw_member( - "types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - abilities_set.append(RoRAbilitySubprocessor.projectile_ability( - line, position=projectile_num)) - abilities_set.append(AoCAbilitySubprocessor.move_projectile_ability( - line, position=projectile_num)) - abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( - line, 7, False, projectile_num)) - # TODO: Death, Despawn - proj_raw_api_object.add_raw_member( - "abilities", abilities_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers_set = [] - - proj_raw_api_object.add_raw_member( - "modifiers", modifiers_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Variants - # ======================================================================= - variants_set = [] - proj_raw_api_object.add_raw_member( - "variants", variants_set, "engine.util.game_entity.GameEntity") - - line.add_raw_api_object(proj_raw_api_object) + ambient_group_to_game_entity = staticmethod(ambient_group_to_game_entity) + building_line_to_game_entity = staticmethod(building_line_to_game_entity) + civ_group_to_civ = staticmethod(civ_group_to_civ) + projectiles_from_line = staticmethod(projectiles_from_line) + tech_group_to_tech = staticmethod(tech_group_to_tech) + terrain_group_to_terrain = staticmethod(terrain_group_to_terrain) + unit_line_to_game_entity = staticmethod(unit_line_to_game_entity) From 50daca1f33a19fa50ed4e5e6c8d4e258f2321f50 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 15:12:20 +0200 Subject: [PATCH 138/163] convert: Refactor RoRPregenSubprocessor into separate files. --- .../conversion/aoc/pregen/__init__.py | 1 - .../conversion/ror/pregen_subprocessor.py | 90 +------------------ 2 files changed, 3 insertions(+), 88 deletions(-) diff --git a/openage/convert/processor/conversion/aoc/pregen/__init__.py b/openage/convert/processor/conversion/aoc/pregen/__init__.py index c9eef96971..07febc40fc 100644 --- a/openage/convert/processor/conversion/aoc/pregen/__init__.py +++ b/openage/convert/processor/conversion/aoc/pregen/__init__.py @@ -4,4 +4,3 @@ Creates nyan objects for things that are hardcoded into the Genie Engine, but configurable in openage, e.g. HP. """ -# Copyright 2025-2025 the openage authors. See copying.md for legal info. diff --git a/openage/convert/processor/conversion/ror/pregen_subprocessor.py b/openage/convert/processor/conversion/ror/pregen_subprocessor.py index 890ad1250b..1b8d655190 100644 --- a/openage/convert/processor/conversion/ror/pregen_subprocessor.py +++ b/openage/convert/processor/conversion/ror/pregen_subprocessor.py @@ -9,13 +9,11 @@ from __future__ import annotations import typing -from ....entity_object.conversion.converter_object import ConverterObjectGroup, \ - RawAPIObject -from ....value_object.conversion.forward_ref import ForwardRef +from ....entity_object.conversion.converter_object import ConverterObjectGroup from ..aoc.pregen_processor import AoCPregenSubprocessor if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_object_container\ + from ....entity_object.conversion.aoc.genie_object_container\ import GenieObjectContainer @@ -44,7 +42,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: AoCPregenSubprocessor.generate_terrain_types(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_path_types(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_resources(full_data_set, pregen_converter_group) - cls.generate_death_condition(full_data_set, pregen_converter_group) + AoCPregenSubprocessor.generate_death_condition(full_data_set, pregen_converter_group) pregen_nyan_objects = full_data_set.pregen_nyan_objects # Create nyan objects from the raw API objects @@ -58,85 +56,3 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: if not pregen_object.is_ready(): raise RuntimeError(f"{repr(pregen_object)}: Pregenerated object is not ready " "for export. Member or object not initialized.") - - @staticmethod - def generate_death_condition( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate DeathCondition objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - # ======================================================================= - # Death condition - # ======================================================================= - logic_parent = "engine.util.logic.LogicElement" - literal_parent = "engine.util.logic.literal.Literal" - interval_parent = "engine.util.logic.literal.type.AttributeBelowValue" - literal_location = "data/util/logic/death/" - - death_ref_in_modpack = "util.logic.literal.death.StandardHealthDeathLiteral" - literal_raw_api_object = RawAPIObject(death_ref_in_modpack, - "StandardHealthDeathLiteral", - api_objects, - literal_location) - literal_raw_api_object.set_filename("death") - literal_raw_api_object.add_raw_parent(interval_parent) - - # Literal will not default to 'True' when it was fulfilled once - literal_raw_api_object.add_raw_member("only_once", False, logic_parent) - - # Scope - scope_forward_ref = ForwardRef(pregen_converter_group, - "util.logic.literal_scope.death.StandardHealthDeathScope") - literal_raw_api_object.add_raw_member("scope", - scope_forward_ref, - literal_parent) - - # Attribute - health_forward_ref = ForwardRef(pregen_converter_group, - "util.attribute.types.Health") - literal_raw_api_object.add_raw_member("attribute", - health_forward_ref, - interval_parent) - - # sidenote: Apparently this is actually HP<1 in Genie - # (https://youtu.be/FdBk8zGbE7U?t=7m16s) - literal_raw_api_object.add_raw_member("threshold", - 1, - interval_parent) - - pregen_converter_group.add_raw_api_object(literal_raw_api_object) - pregen_nyan_objects.update({death_ref_in_modpack: literal_raw_api_object}) - - # LiteralScope - scope_parent = "engine.util.logic.literal_scope.LiteralScope" - self_scope_parent = "engine.util.logic.literal_scope.type.Self" - - death_scope_ref_in_modpack = "util.logic.literal_scope.death.StandardHealthDeathScope" - scope_raw_api_object = RawAPIObject(death_scope_ref_in_modpack, - "StandardHealthDeathScope", - api_objects) - scope_location = ForwardRef(pregen_converter_group, death_ref_in_modpack) - scope_raw_api_object.set_location(scope_location) - scope_raw_api_object.add_raw_parent(self_scope_parent) - - scope_diplomatic_stances = [api_objects["engine.util.diplomatic_stance.type.Self"]] - scope_raw_api_object.add_raw_member("stances", - scope_diplomatic_stances, - scope_parent) - - pregen_converter_group.add_raw_api_object(scope_raw_api_object) - pregen_nyan_objects.update({death_scope_ref_in_modpack: scope_raw_api_object}) From 821e53893e5c25b6d0f9399e9f629ee6049650ec Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 15:45:45 +0200 Subject: [PATCH 139/163] convert: Refactor RoRProcessor into separate files. --- .../processor/conversion/ror/CMakeLists.txt | 1 + .../conversion/ror/main/CMakeLists.txt | 7 + .../processor/conversion/ror/main/__init__.py | 5 + .../ror/main/extract/CMakeLists.txt | 5 + .../conversion/ror/main/extract/__init__.py | 5 + .../conversion/ror/main/extract/sound.py | 29 + .../conversion/ror/main/extract/unit.py | 54 ++ .../conversion/ror/main/groups/CMakeLists.txt | 7 + .../conversion/ror/main/groups/__init__.py | 5 + .../ror/main/groups/ambient_group.py | 31 + .../conversion/ror/main/groups/entity_line.py | 181 ++++++ .../conversion/ror/main/groups/tech_group.py | 152 +++++ .../ror/main/groups/variant_group.py | 32 ++ .../conversion/ror/main/link/CMakeLists.txt | 5 + .../conversion/ror/main/link/__init__.py | 5 + .../conversion/ror/main/link/garrison.py | 52 ++ .../conversion/ror/main/link/repairable.py | 48 ++ .../processor/conversion/ror/processor.py | 543 +----------------- 18 files changed, 655 insertions(+), 512 deletions(-) create mode 100644 openage/convert/processor/conversion/ror/main/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/main/__init__.py create mode 100644 openage/convert/processor/conversion/ror/main/extract/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/main/extract/__init__.py create mode 100644 openage/convert/processor/conversion/ror/main/extract/sound.py create mode 100644 openage/convert/processor/conversion/ror/main/extract/unit.py create mode 100644 openage/convert/processor/conversion/ror/main/groups/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/main/groups/__init__.py create mode 100644 openage/convert/processor/conversion/ror/main/groups/ambient_group.py create mode 100644 openage/convert/processor/conversion/ror/main/groups/entity_line.py create mode 100644 openage/convert/processor/conversion/ror/main/groups/tech_group.py create mode 100644 openage/convert/processor/conversion/ror/main/groups/variant_group.py create mode 100644 openage/convert/processor/conversion/ror/main/link/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/main/link/__init__.py create mode 100644 openage/convert/processor/conversion/ror/main/link/garrison.py create mode 100644 openage/convert/processor/conversion/ror/main/link/repairable.py diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index 4ba3c6bd8e..61ca99acea 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -17,4 +17,5 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(auxiliary) add_subdirectory(civ) +add_subdirectory(main) add_subdirectory(nyan) diff --git a/openage/convert/processor/conversion/ror/main/CMakeLists.txt b/openage/convert/processor/conversion/ror/main/CMakeLists.txt new file mode 100644 index 0000000000..8de84d720c --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/CMakeLists.txt @@ -0,0 +1,7 @@ +add_py_modules( + __init__.py +) + +add_subdirectory(extract) +add_subdirectory(groups) +add_subdirectory(link) diff --git a/openage/convert/processor/conversion/ror/main/__init__.py b/openage/convert/processor/conversion/ror/main/__init__.py new file mode 100644 index 0000000000..db2f55290f --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create lines and groups fron extracted RoR data. +""" diff --git a/openage/convert/processor/conversion/ror/main/extract/CMakeLists.txt b/openage/convert/processor/conversion/ror/main/extract/CMakeLists.txt new file mode 100644 index 0000000000..6913317868 --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/extract/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py + sound.py + unit.py +) diff --git a/openage/convert/processor/conversion/ror/main/extract/__init__.py b/openage/convert/processor/conversion/ror/main/extract/__init__.py new file mode 100644 index 0000000000..b88655a0c8 --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/extract/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract AoC data from the game dataset and prepares it for conversion. +""" diff --git a/openage/convert/processor/conversion/ror/main/extract/sound.py b/openage/convert/processor/conversion/ror/main/extract/sound.py new file mode 100644 index 0000000000..2020cebd0e --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/extract/sound.py @@ -0,0 +1,29 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract sounds from the RoR data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.ror.genie_sound import RoRSound + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + from ......value_object.read.value_members import ArrayMember + + +def extract_genie_sounds(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract sound definitions from the game data. + + :param gamespec: Gamedata from empires.dat file. + """ + # call hierarchy: wrapper[0]->sounds + raw_sounds = gamespec[0]["sounds"].value + for raw_sound in raw_sounds: + sound_id = raw_sound["sound_id"].value + sound_members = raw_sound.value + + sound = RoRSound(sound_id, full_data_set, members=sound_members) + full_data_set.genie_sounds.update({sound.get_id(): sound}) diff --git a/openage/convert/processor/conversion/ror/main/extract/unit.py b/openage/convert/processor/conversion/ror/main/extract/unit.py new file mode 100644 index 0000000000..6dcb6376a6 --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/extract/unit.py @@ -0,0 +1,54 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Extract units from the RoR data. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieUnitObject + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + from ......value_object.read.value_members import ArrayMember + + +def extract_genie_units(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Extract units from the game data. + + :param gamespec: Gamedata from empires.dat file. + """ + # Units are stored in the civ container. + # In RoR the normal civs are not subsets of the Gaia civ, so we search units from + # Gaia and one player civ (egyptiians). + raw_units = [] + + # Gaia units + raw_units.extend(gamespec[0]["civs"][0]["units"].value) + + # Egyptians + raw_units.extend(gamespec[0]["civs"][1]["units"].value) + + for raw_unit in raw_units: + unit_id = raw_unit["id0"].value + + if unit_id in full_data_set.genie_units.keys(): + continue + + unit_members = raw_unit.value + # Turn attack and armor into containers to make diffing work + if "attacks" in unit_members.keys(): + attacks_member = unit_members.pop("attacks") + attacks_member = attacks_member.get_container("type_id") + armors_member = unit_members.pop("armors") + armors_member = armors_member.get_container("type_id") + + unit_members.update({"attacks": attacks_member}) + unit_members.update({"armors": armors_member}) + + unit = GenieUnitObject(unit_id, full_data_set, members=unit_members) + full_data_set.genie_units.update({unit.get_id(): unit}) + + # Sort the dict to make debugging easier :) + full_data_set.genie_units = dict(sorted(full_data_set.genie_units.items())) diff --git a/openage/convert/processor/conversion/ror/main/groups/CMakeLists.txt b/openage/convert/processor/conversion/ror/main/groups/CMakeLists.txt new file mode 100644 index 0000000000..0250ac6b57 --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/groups/CMakeLists.txt @@ -0,0 +1,7 @@ +add_py_modules( + __init__.py + ambient_group.py + entity_line.py + tech_group.py + variant_group.py +) diff --git a/openage/convert/processor/conversion/ror/main/groups/__init__.py b/openage/convert/processor/conversion/ror/main/groups/__init__.py new file mode 100644 index 0000000000..1246e6bf5f --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/groups/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create lines and groups fron extracted AoC data. +""" diff --git a/openage/convert/processor/conversion/ror/main/groups/ambient_group.py b/openage/convert/processor/conversion/ror/main/groups/ambient_group.py new file mode 100644 index 0000000000..70902a41dd --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/groups/ambient_group.py @@ -0,0 +1,31 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create ambient groups from genie units. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.ror.genie_unit import RoRAmbientGroup +from ......value_object.conversion.ror.internal_nyan_names import AMBIENT_GROUP_LOOKUPS + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_ambient_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create ambient groups, mostly for resources and scenery. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + ambient_ids = AMBIENT_GROUP_LOOKUPS.keys() + genie_units = full_data_set.genie_units + + for ambient_id in ambient_ids: + ambient_group = RoRAmbientGroup(ambient_id, full_data_set) + ambient_group.add_unit(genie_units[ambient_id]) + full_data_set.ambient_groups.update({ambient_group.get_id(): ambient_group}) + full_data_set.unit_ref.update({ambient_id: ambient_group}) diff --git a/openage/convert/processor/conversion/ror/main/groups/entity_line.py b/openage/convert/processor/conversion/ror/main/groups/entity_line.py new file mode 100644 index 0000000000..d9ce861f38 --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/groups/entity_line.py @@ -0,0 +1,181 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create unit/building lines from genie buildings. +""" + + +from __future__ import annotations +import typing + +from ......entity_object.conversion.ror.genie_unit import RoRUnitTaskGroup, \ + RoRUnitLineGroup, RoRBuildingLineGroup, RoRVillagerGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + from ......value_object.read.value_members import ArrayMember + + +def create_entity_lines(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: + """ + Sort units/buildings into lines, based on information from techs and civs. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + # Search a player civ (egyptians) for the starting units + player_civ_units = gamespec[0]["civs"][1]["units"].value + task_group_ids = set() + villager_unit_ids = set() + + for raw_unit in player_civ_units.values(): + unit_id = raw_unit["id0"].value + enabled = raw_unit["enabled"].value + entity = full_data_set.genie_units[unit_id] + + if not enabled: + # Unlocked by tech + continue + + unit_type = entity["unit_type"].value + + if unit_type == 70: + if entity.has_member("task_group") and\ + entity["task_group"].value > 0: + task_group_id = entity["task_group"].value + villager_unit_ids.add(unit_id) + + if task_group_id in task_group_ids: + task_group = full_data_set.task_groups[task_group_id] + task_group.add_unit(entity) + + else: + if task_group_id == 1: + line_id = RoRUnitTaskGroup.male_line_id + + task_group = RoRUnitTaskGroup(line_id, task_group_id, -1, full_data_set) + task_group.add_unit(entity) + task_group_ids.add(task_group_id) + full_data_set.task_groups.update({task_group_id: task_group}) + + else: + unit_line = RoRUnitLineGroup(unit_id, -1, full_data_set) + unit_line.add_unit(entity) + full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) + full_data_set.unit_ref.update({unit_id: unit_line}) + + elif unit_type == 80: + building_line = RoRBuildingLineGroup(unit_id, -1, full_data_set) + building_line.add_unit(entity) + full_data_set.building_lines.update({building_line.get_id(): building_line}) + full_data_set.unit_ref.update({unit_id: building_line}) + + # Create the villager task group + villager = RoRVillagerGroup(118, task_group_ids, full_data_set) + full_data_set.unit_lines.update({villager.get_id(): villager}) + full_data_set.villager_groups.update({villager.get_id(): villager}) + for unit_id in villager_unit_ids: + full_data_set.unit_ref.update({unit_id: villager}) + + # Other units unlocks through techs + unit_unlocks = full_data_set.unit_unlocks + for unit_unlock in unit_unlocks.values(): + line_id = unit_unlock.get_line_id() + unit = full_data_set.genie_units[line_id] + + unit_line = RoRUnitLineGroup(line_id, unit_unlock.get_id(), full_data_set) + unit_line.add_unit(unit) + full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) + full_data_set.unit_ref.update({line_id: unit_line}) + + # Check if the tech unlocks other lines + # TODO: Make this cleaner + unlock_effects = unit_unlock.get_effects(effect_type=2) + for unlock_effect in unlock_effects: + line_id = unlock_effect["attr_a"].value + unit = full_data_set.genie_units[line_id] + + if line_id not in full_data_set.unit_lines.keys(): + unit_line = RoRUnitLineGroup(line_id, unit_unlock.get_id(), full_data_set) + unit_line.add_unit(unit) + full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) + full_data_set.unit_ref.update({line_id: unit_line}) + + # Upgraded units in a line + unit_upgrades = full_data_set.unit_upgrades + for unit_upgrade in unit_upgrades.values(): + line_id = unit_upgrade.get_line_id() + target_id = unit_upgrade.get_upgrade_target_id() + unit = full_data_set.genie_units[target_id] + + # Find the previous unit in the line + required_techs = unit_upgrade.tech["required_techs"].value + for required_tech in required_techs: + required_tech_id = required_tech.value + if required_tech_id in full_data_set.unit_unlocks.keys(): + source_id = full_data_set.unit_unlocks[required_tech_id].get_line_id() + break + + if required_tech_id in full_data_set.unit_upgrades.keys(): + source_id = full_data_set.unit_upgrades[required_tech_id].get_upgrade_target_id( + ) + break + + unit_line = full_data_set.unit_lines[line_id] + unit_line.add_unit(unit, after=source_id) + full_data_set.unit_ref.update({target_id: unit_line}) + + # Other buildings unlocks through techs + building_unlocks = full_data_set.building_unlocks + for building_unlock in building_unlocks.values(): + line_id = building_unlock.get_line_id() + building = full_data_set.genie_units[line_id] + + building_line = RoRBuildingLineGroup(line_id, building_unlock.get_id(), full_data_set) + building_line.add_unit(building) + full_data_set.building_lines.update({building_line.get_id(): building_line}) + full_data_set.unit_ref.update({line_id: building_line}) + + # Upgraded buildings through techs + building_upgrades = full_data_set.building_upgrades + for building_upgrade in building_upgrades.values(): + line_id = building_upgrade.get_line_id() + target_id = building_upgrade.get_upgrade_target_id() + unit = full_data_set.genie_units[target_id] + + # Find the previous unit in the line + required_techs = building_upgrade.tech["required_techs"].value + for required_tech in required_techs: + required_tech_id = required_tech.value + if required_tech_id in full_data_set.building_unlocks.keys(): + source_id = full_data_set.building_unlocks[required_tech_id].get_line_id() + break + + if required_tech_id in full_data_set.building_upgrades.keys(): + source_id = full_data_set.building_upgrades[required_tech_id].get_upgrade_target_id( + ) + break + + building_line = full_data_set.building_lines[line_id] + building_line.add_unit(unit, after=source_id) + full_data_set.unit_ref.update({target_id: building_line}) + + # Upgraded units/buildings through age ups + age_ups = full_data_set.age_upgrades + for age_up in age_ups.values(): + effects = age_up.get_effects(effect_type=3) + for effect in effects: + source_id = effect["attr_a"].value + target_id = effect["attr_b"].value + unit = full_data_set.genie_units[target_id] + + if source_id in full_data_set.building_lines.keys(): + building_line = full_data_set.building_lines[source_id] + building_line.add_unit(unit, after=source_id) + full_data_set.unit_ref.update({target_id: building_line}) + + elif source_id in full_data_set.unit_lines.keys(): + unit_line = full_data_set.unit_lines[source_id] + unit_line.add_unit(unit, after=source_id) + full_data_set.unit_ref.update({target_id: unit_line}) diff --git a/openage/convert/processor/conversion/ror/main/groups/tech_group.py b/openage/convert/processor/conversion/ror/main/groups/tech_group.py new file mode 100644 index 0000000000..63632d90ce --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/groups/tech_group.py @@ -0,0 +1,152 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create tech groups from genie techs. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_tech import InitiatedTech +from ......entity_object.conversion.ror.genie_tech import RoRStatUpgrade, \ + RoRBuildingLineUpgrade, RoRUnitLineUpgrade, RoRBuildingUnlock, RoRUnitUnlock, \ + RoRAgeUpgrade + + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_tech_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create techs from tech connections and unit upgrades/unlocks + from unit connections. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + genie_techs = full_data_set.genie_techs + + for tech_id, tech in genie_techs.items(): + tech_type = tech["tech_type"].value + + # Test if a tech exist and skip it if it doesn't + required_techs = tech["required_techs"].value + if all(required_tech.value == 0 for required_tech in required_techs): + # If all required techs are tech ID 0, the tech doesnt exist + continue + + effect_bundle_id = tech["tech_effect_id"].value + + if effect_bundle_id == -1: + continue + + effect_bundle = full_data_set.genie_effect_bundles[effect_bundle_id] + + # Ignore techs without effects + if len(effect_bundle.get_effects()) == 0: + continue + + # Town Center techs (only age ups) + if tech_type == 12: + # Age ID is set as resource value + setr_effects = effect_bundle.get_effects(effect_type=1) + for effect in setr_effects: + resource_id = effect["attr_a"].value + + if resource_id == 6: + age_id = int(effect["attr_d"].value) + break + + age_up = RoRAgeUpgrade(tech_id, age_id, full_data_set) + full_data_set.tech_groups.update({age_up.get_id(): age_up}) + full_data_set.age_upgrades.update({age_up.get_id(): age_up}) + + else: + effects = effect_bundle.get_effects() + for effect in effects: + # Enabling techs + if effect.get_type() == 2: + unit_id = effect["attr_a"].value + unit = full_data_set.genie_units[unit_id] + unit_type = unit["unit_type"].value + + if unit_type == 70: + unit_unlock = RoRUnitUnlock(tech_id, unit_id, full_data_set) + full_data_set.tech_groups.update( + {unit_unlock.get_id(): unit_unlock} + ) + full_data_set.unit_unlocks.update( + {unit_unlock.get_id(): unit_unlock} + ) + break + + if unit_type == 80: + building_unlock = RoRBuildingUnlock(tech_id, unit_id, full_data_set) + full_data_set.tech_groups.update( + {building_unlock.get_id(): building_unlock} + ) + full_data_set.building_unlocks.update( + {building_unlock.get_id(): building_unlock} + ) + break + + # Upgrades + elif effect.get_type() == 3: + source_unit_id = effect["attr_a"].value + target_unit_id = effect["attr_b"].value + unit = full_data_set.genie_units[source_unit_id] + unit_type = unit["unit_type"].value + + if unit_type == 70: + unit_upgrade = RoRUnitLineUpgrade(tech_id, + source_unit_id, + target_unit_id, + full_data_set) + full_data_set.tech_groups.update( + {unit_upgrade.get_id(): unit_upgrade} + ) + full_data_set.unit_upgrades.update( + {unit_upgrade.get_id(): unit_upgrade} + ) + break + + if unit_type == 80: + building_upgrade = RoRBuildingLineUpgrade(tech_id, + source_unit_id, + target_unit_id, + full_data_set) + full_data_set.tech_groups.update( + {building_upgrade.get_id(): building_upgrade} + ) + full_data_set.building_upgrades.update( + {building_upgrade.get_id(): building_upgrade} + ) + break + + else: + # Anything else must be a stat upgrade + stat_up = RoRStatUpgrade(tech_id, full_data_set) + full_data_set.tech_groups.update({stat_up.get_id(): stat_up}) + full_data_set.stat_upgrades.update({stat_up.get_id(): stat_up}) + + # Initiated techs are stored with buildings + genie_units = full_data_set.genie_units + + for genie_unit in genie_units.values(): + if not genie_unit.has_member("research_id"): + continue + + building_id = genie_unit["id0"].value + initiated_tech_id = genie_unit["research_id"].value + + if initiated_tech_id == -1: + continue + + if building_id not in full_data_set.building_lines.keys(): + # Skips upgraded buildings (which initiate the same techs) + continue + + initiated_tech = InitiatedTech(initiated_tech_id, building_id, full_data_set) + full_data_set.tech_groups.update({initiated_tech.get_id(): initiated_tech}) + full_data_set.initiated_techs.update({initiated_tech.get_id(): initiated_tech}) diff --git a/openage/convert/processor/conversion/ror/main/groups/variant_group.py b/openage/convert/processor/conversion/ror/main/groups/variant_group.py new file mode 100644 index 0000000000..e45f74cb35 --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/groups/variant_group.py @@ -0,0 +1,32 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create variant groups from genie units. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.ror.genie_unit import RoRVariantGroup +from ......value_object.conversion.ror.internal_nyan_names import VARIANT_GROUP_LOOKUPS + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_variant_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create variant groups. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + variants = VARIANT_GROUP_LOOKUPS + + for group_id, variant in variants.items(): + variant_group = RoRVariantGroup(group_id, full_data_set) + full_data_set.variant_groups.update({variant_group.get_id(): variant_group}) + + for variant_id in variant[2]: + variant_group.add_unit(full_data_set.genie_units[variant_id]) + full_data_set.unit_ref.update({variant_id: variant_group}) diff --git a/openage/convert/processor/conversion/ror/main/link/CMakeLists.txt b/openage/convert/processor/conversion/ror/main/link/CMakeLists.txt new file mode 100644 index 0000000000..24513df1c3 --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/link/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py + garrison.py + repairable.py +) diff --git a/openage/convert/processor/conversion/ror/main/link/__init__.py b/openage/convert/processor/conversion/ror/main/link/__init__.py new file mode 100644 index 0000000000..7598391f4e --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/link/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link AoC data to the game entities created from lines. +""" diff --git a/openage/convert/processor/conversion/ror/main/link/garrison.py b/openage/convert/processor/conversion/ror/main/link/garrison.py new file mode 100644 index 0000000000..7fd01b4a22 --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/link/garrison.py @@ -0,0 +1,52 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link garrisonable lines to their garrison locations and vice versa. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_garrison(full_data_set: GenieObjectContainer) -> None: + """ + Link a garrison unit to the lines that are stored and vice versa. This is done + to provide quick access during conversion. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + unit_lines = full_data_set.unit_lines + + garrison_class_assignments = {} + + for unit_line in unit_lines.values(): + head_unit = unit_line.get_head_unit() + + unit_commands = head_unit["unit_commands"].value + for command in unit_commands: + command_type = command["type"].value + + if not command_type == 3: + continue + + class_id = command["class_id"].value + + if class_id in garrison_class_assignments: + garrison_class_assignments[class_id].append(unit_line) + + else: + garrison_class_assignments[class_id] = [unit_line] + + break + + for garrison in unit_lines.values(): + class_id = garrison.get_class_id() + + if class_id in garrison_class_assignments: + for line in garrison_class_assignments[class_id]: + garrison.garrison_entities.append(line) + line.garrison_locations.append(garrison) diff --git a/openage/convert/processor/conversion/ror/main/link/repairable.py b/openage/convert/processor/conversion/ror/main/link/repairable.py new file mode 100644 index 0000000000..3ae0886ff0 --- /dev/null +++ b/openage/convert/processor/conversion/ror/main/link/repairable.py @@ -0,0 +1,48 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link repairable units/buildings to villager groups. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_repairables(full_data_set: GenieObjectContainer) -> None: + """ + Set units/buildings as repairable + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + villager_groups = full_data_set.villager_groups + + repair_lines = {} + repair_lines.update(full_data_set.unit_lines) + repair_lines.update(full_data_set.building_lines) + + repair_classes = [] + for villager in villager_groups.values(): + repair_unit = villager.get_units_with_command(106)[0] + unit_commands = repair_unit["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + if type_id != 106: + continue + + class_id = command["class_id"].value + if class_id == -1: + # Buildings/Siege + repair_classes.append(3) + repair_classes.append(13) + + else: + repair_classes.append(class_id) + + for repair_line in repair_lines.values(): + if repair_line.get_class_id() in repair_classes: + repair_line.repairable = True diff --git a/openage/convert/processor/conversion/ror/processor.py b/openage/convert/processor/conversion/ror/processor.py index d6220753fa..5875bca21d 100644 --- a/openage/convert/processor/conversion/ror/processor.py +++ b/openage/convert/processor/conversion/ror/processor.py @@ -1,6 +1,4 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=line-too-long,too-many-lines,too-many-branches,too-many-statements,too-many-locals +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Convert data from RoR to openage formats. """ @@ -9,32 +7,29 @@ from .....log import info from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer -from ....entity_object.conversion.aoc.genie_tech import InitiatedTech -from ....entity_object.conversion.aoc.genie_unit import GenieUnitObject -from ....entity_object.conversion.ror.genie_sound import RoRSound -from ....entity_object.conversion.ror.genie_tech import RoRStatUpgrade, \ - RoRBuildingLineUpgrade, RoRUnitLineUpgrade, RoRBuildingUnlock, RoRUnitUnlock, \ - RoRAgeUpgrade -from ....entity_object.conversion.ror.genie_unit import RoRUnitTaskGroup, \ - RoRUnitLineGroup, RoRBuildingLineGroup, RoRVillagerGroup, RoRAmbientGroup, \ - RoRVariantGroup -from ....service.debug_info import debug_converter_objects, \ - debug_converter_object_groups +from ....service.debug_info import debug_converter_objects, debug_converter_object_groups from ....service.read.nyan_api_loader import load_api -from ....value_object.conversion.ror.internal_nyan_names import AMBIENT_GROUP_LOOKUPS, \ - VARIANT_GROUP_LOOKUPS from ..aoc.processor import AoCProcessor from .media_subprocessor import RoRMediaSubprocessor from .modpack_subprocessor import RoRModpackSubprocessor from .nyan_subprocessor import RoRNyanSubprocessor from .pregen_subprocessor import RoRPregenSubprocessor +from .main.extract.sound import extract_genie_sounds +from .main.extract.unit import extract_genie_units +from .main.groups.ambient_group import create_ambient_groups +from .main.groups.entity_line import create_entity_lines +from .main.groups.tech_group import create_tech_groups +from .main.groups.variant_group import create_variant_groups +from .main.link.garrison import link_garrison +from .main.link.repairable import link_repairables + if typing.TYPE_CHECKING: from argparse import Namespace - from openage.convert.entity_object.conversion.stringresource import StringResource - from openage.convert.entity_object.conversion.modpack import Modpack - from openage.convert.value_object.read.value_members import ArrayMember - from openage.convert.value_object.init.game_version import GameVersion + from ....entity_object.conversion.stringresource import StringResource + from ....entity_object.conversion.modpack import Modpack + from ....value_object.read.value_members import ArrayMember + from ....value_object.init.game_version import GameVersion class RoRProcessor: @@ -108,13 +103,13 @@ def _pre_processor( info("Extracting Genie data...") - cls.extract_genie_units(gamespec, dataset) + extract_genie_units(gamespec, dataset) AoCProcessor.extract_genie_techs(gamespec, dataset) AoCProcessor.extract_genie_effect_bundles(gamespec, dataset) AoCProcessor.sanitize_effect_bundles(dataset) AoCProcessor.extract_genie_civs(gamespec, dataset) AoCProcessor.extract_genie_graphics(gamespec, dataset) - cls.extract_genie_sounds(gamespec, dataset) + extract_genie_sounds(gamespec, dataset) AoCProcessor.extract_genie_terrains(gamespec, dataset) AoCProcessor.extract_genie_restrictions(gamespec, dataset) @@ -136,10 +131,10 @@ def _processor(cls, gamespec: ArrayMember, full_data_set: GenieObjectContainer) info("Creating API-like objects...") - cls.create_tech_groups(full_data_set) - cls.create_entity_lines(gamespec, full_data_set) - cls.create_ambient_groups(full_data_set) - cls.create_variant_groups(full_data_set) + create_tech_groups(full_data_set) + create_entity_lines(gamespec, full_data_set) + create_ambient_groups(full_data_set) + create_variant_groups(full_data_set) AoCProcessor.create_terrain_groups(full_data_set) AoCProcessor.create_civ_groups(full_data_set) @@ -148,9 +143,9 @@ def _processor(cls, gamespec: ArrayMember, full_data_set: GenieObjectContainer) AoCProcessor.link_creatables(full_data_set) AoCProcessor.link_researchables(full_data_set) AoCProcessor.link_gatherers_to_dropsites(full_data_set) - cls.link_garrison(full_data_set) + link_garrison(full_data_set) AoCProcessor.link_trade_posts(full_data_set) - cls.link_repairables(full_data_set) + link_repairables(full_data_set) info("Generating auxiliary objects...") @@ -178,487 +173,11 @@ def _post_processor(cls, full_data_set: GenieObjectContainer) -> list[Modpack]: return RoRModpackSubprocessor.get_modpacks(full_data_set) - @staticmethod - def extract_genie_units(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract units from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # Units are stored in the civ container. - # In RoR the normal civs are not subsets of the Gaia civ, so we search units from - # Gaia and one player civ (egyptiians). - raw_units = [] - - # Gaia units - # call hierarchy: wrapper[0]->civs[0]->units - raw_units.extend(gamespec[0]["civs"][0]["units"].value) - - # Egyptians - # call hierarchy: wrapper[0]->civs[1]->units - raw_units.extend(gamespec[0]["civs"][1]["units"].value) - - for raw_unit in raw_units: - unit_id = raw_unit["id0"].value - - if unit_id in full_data_set.genie_units.keys(): - continue - - unit_members = raw_unit.value - # Turn attack and armor into containers to make diffing work - if "attacks" in unit_members.keys(): - attacks_member = unit_members.pop("attacks") - attacks_member = attacks_member.get_container("type_id") - armors_member = unit_members.pop("armors") - armors_member = armors_member.get_container("type_id") - - unit_members.update({"attacks": attacks_member}) - unit_members.update({"armors": armors_member}) - - unit = GenieUnitObject(unit_id, full_data_set, members=unit_members) - full_data_set.genie_units.update({unit.get_id(): unit}) - - # Sort the dict to make debugging easier :) - full_data_set.genie_units = dict(sorted(full_data_set.genie_units.items())) - - @staticmethod - def extract_genie_sounds(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Extract sound definitions from the game data. - - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - """ - # call hierarchy: wrapper[0]->sounds - raw_sounds = gamespec[0]["sounds"].value - for raw_sound in raw_sounds: - sound_id = raw_sound["sound_id"].value - sound_members = raw_sound.value - - sound = RoRSound(sound_id, full_data_set, members=sound_members) - full_data_set.genie_sounds.update({sound.get_id(): sound}) - - @staticmethod - def create_entity_lines(gamespec: ArrayMember, full_data_set: GenieObjectContainer) -> None: - """ - Sort units/buildings into lines, based on information from techs and civs. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - # Search a player civ (egyptians) for the starting units - player_civ_units = gamespec[0]["civs"][1]["units"].value - task_group_ids = set() - villager_unit_ids = set() - - for raw_unit in player_civ_units.values(): - unit_id = raw_unit["id0"].value - enabled = raw_unit["enabled"].value - entity = full_data_set.genie_units[unit_id] - - if not enabled: - # Unlocked by tech - continue - - unit_type = entity["unit_type"].value - - if unit_type == 70: - if entity.has_member("task_group") and\ - entity["task_group"].value > 0: - task_group_id = entity["task_group"].value - villager_unit_ids.add(unit_id) - - if task_group_id in task_group_ids: - task_group = full_data_set.task_groups[task_group_id] - task_group.add_unit(entity) - - else: - if task_group_id == 1: - line_id = RoRUnitTaskGroup.male_line_id - - task_group = RoRUnitTaskGroup(line_id, task_group_id, -1, full_data_set) - task_group.add_unit(entity) - task_group_ids.add(task_group_id) - full_data_set.task_groups.update({task_group_id: task_group}) - - else: - unit_line = RoRUnitLineGroup(unit_id, -1, full_data_set) - unit_line.add_unit(entity) - full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) - full_data_set.unit_ref.update({unit_id: unit_line}) - - elif unit_type == 80: - building_line = RoRBuildingLineGroup(unit_id, -1, full_data_set) - building_line.add_unit(entity) - full_data_set.building_lines.update({building_line.get_id(): building_line}) - full_data_set.unit_ref.update({unit_id: building_line}) - - # Create the villager task group - villager = RoRVillagerGroup(118, task_group_ids, full_data_set) - full_data_set.unit_lines.update({villager.get_id(): villager}) - full_data_set.villager_groups.update({villager.get_id(): villager}) - for unit_id in villager_unit_ids: - full_data_set.unit_ref.update({unit_id: villager}) - - # Other units unlocks through techs - unit_unlocks = full_data_set.unit_unlocks - for unit_unlock in unit_unlocks.values(): - line_id = unit_unlock.get_line_id() - unit = full_data_set.genie_units[line_id] - - unit_line = RoRUnitLineGroup(line_id, unit_unlock.get_id(), full_data_set) - unit_line.add_unit(unit) - full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) - full_data_set.unit_ref.update({line_id: unit_line}) - - # Check if the tech unlocks other lines - # TODO: Make this cleaner - unlock_effects = unit_unlock.get_effects(effect_type=2) - for unlock_effect in unlock_effects: - line_id = unlock_effect["attr_a"].value - unit = full_data_set.genie_units[line_id] - - if line_id not in full_data_set.unit_lines.keys(): - unit_line = RoRUnitLineGroup(line_id, unit_unlock.get_id(), full_data_set) - unit_line.add_unit(unit) - full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) - full_data_set.unit_ref.update({line_id: unit_line}) - - # Upgraded units in a line - unit_upgrades = full_data_set.unit_upgrades - for unit_upgrade in unit_upgrades.values(): - line_id = unit_upgrade.get_line_id() - target_id = unit_upgrade.get_upgrade_target_id() - unit = full_data_set.genie_units[target_id] - - # Find the previous unit in the line - required_techs = unit_upgrade.tech["required_techs"].value - for required_tech in required_techs: - required_tech_id = required_tech.value - if required_tech_id in full_data_set.unit_unlocks.keys(): - source_id = full_data_set.unit_unlocks[required_tech_id].get_line_id() - break - - if required_tech_id in full_data_set.unit_upgrades.keys(): - source_id = full_data_set.unit_upgrades[required_tech_id].get_upgrade_target_id( - ) - break - - unit_line = full_data_set.unit_lines[line_id] - unit_line.add_unit(unit, after=source_id) - full_data_set.unit_ref.update({target_id: unit_line}) - - # Other buildings unlocks through techs - building_unlocks = full_data_set.building_unlocks - for building_unlock in building_unlocks.values(): - line_id = building_unlock.get_line_id() - building = full_data_set.genie_units[line_id] - - building_line = RoRBuildingLineGroup(line_id, building_unlock.get_id(), full_data_set) - building_line.add_unit(building) - full_data_set.building_lines.update({building_line.get_id(): building_line}) - full_data_set.unit_ref.update({line_id: building_line}) - - # Upgraded buildings through techs - building_upgrades = full_data_set.building_upgrades - for building_upgrade in building_upgrades.values(): - line_id = building_upgrade.get_line_id() - target_id = building_upgrade.get_upgrade_target_id() - unit = full_data_set.genie_units[target_id] - - # Find the previous unit in the line - required_techs = building_upgrade.tech["required_techs"].value - for required_tech in required_techs: - required_tech_id = required_tech.value - if required_tech_id in full_data_set.building_unlocks.keys(): - source_id = full_data_set.building_unlocks[required_tech_id].get_line_id() - break - - if required_tech_id in full_data_set.building_upgrades.keys(): - source_id = full_data_set.building_upgrades[required_tech_id].get_upgrade_target_id( - ) - break - - building_line = full_data_set.building_lines[line_id] - building_line.add_unit(unit, after=source_id) - full_data_set.unit_ref.update({target_id: building_line}) - - # Upgraded units/buildings through age ups - age_ups = full_data_set.age_upgrades - for age_up in age_ups.values(): - effects = age_up.get_effects(effect_type=3) - for effect in effects: - source_id = effect["attr_a"].value - target_id = effect["attr_b"].value - unit = full_data_set.genie_units[target_id] - - if source_id in full_data_set.building_lines.keys(): - building_line = full_data_set.building_lines[source_id] - building_line.add_unit(unit, after=source_id) - full_data_set.unit_ref.update({target_id: building_line}) - - elif source_id in full_data_set.unit_lines.keys(): - unit_line = full_data_set.unit_lines[source_id] - unit_line.add_unit(unit, after=source_id) - full_data_set.unit_ref.update({target_id: unit_line}) - - @staticmethod - def create_ambient_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create ambient groups, mostly for resources and scenery. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - ambient_ids = AMBIENT_GROUP_LOOKUPS.keys() - genie_units = full_data_set.genie_units - - for ambient_id in ambient_ids: - ambient_group = RoRAmbientGroup(ambient_id, full_data_set) - ambient_group.add_unit(genie_units[ambient_id]) - full_data_set.ambient_groups.update({ambient_group.get_id(): ambient_group}) - full_data_set.unit_ref.update({ambient_id: ambient_group}) - - @staticmethod - def create_variant_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create variant groups. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - variants = VARIANT_GROUP_LOOKUPS - - for group_id, variant in variants.items(): - variant_group = RoRVariantGroup(group_id, full_data_set) - full_data_set.variant_groups.update({variant_group.get_id(): variant_group}) - - for variant_id in variant[2]: - variant_group.add_unit(full_data_set.genie_units[variant_id]) - full_data_set.unit_ref.update({variant_id: variant_group}) - - @staticmethod - def create_tech_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create techs from tech connections and unit upgrades/unlocks - from unit connections. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - genie_techs = full_data_set.genie_techs - - for tech_id, tech in genie_techs.items(): - tech_type = tech["tech_type"].value - - # Test if a tech exist and skip it if it doesn't - required_techs = tech["required_techs"].value - if all(required_tech.value == 0 for required_tech in required_techs): - # If all required techs are tech ID 0, the tech doesnt exist - continue - - effect_bundle_id = tech["tech_effect_id"].value - - if effect_bundle_id == -1: - continue - - effect_bundle = full_data_set.genie_effect_bundles[effect_bundle_id] - - # Ignore techs without effects - if len(effect_bundle.get_effects()) == 0: - continue - - # Town Center techs (only age ups) - if tech_type == 12: - # Age ID is set as resource value - setr_effects = effect_bundle.get_effects(effect_type=1) - for effect in setr_effects: - resource_id = effect["attr_a"].value - - if resource_id == 6: - age_id = int(effect["attr_d"].value) - break - - age_up = RoRAgeUpgrade(tech_id, age_id, full_data_set) - full_data_set.tech_groups.update({age_up.get_id(): age_up}) - full_data_set.age_upgrades.update({age_up.get_id(): age_up}) - - else: - effects = effect_bundle.get_effects() - for effect in effects: - # Enabling techs - if effect.get_type() == 2: - unit_id = effect["attr_a"].value - unit = full_data_set.genie_units[unit_id] - unit_type = unit["unit_type"].value - - if unit_type == 70: - unit_unlock = RoRUnitUnlock(tech_id, unit_id, full_data_set) - full_data_set.tech_groups.update( - {unit_unlock.get_id(): unit_unlock} - ) - full_data_set.unit_unlocks.update( - {unit_unlock.get_id(): unit_unlock} - ) - break - - if unit_type == 80: - building_unlock = RoRBuildingUnlock(tech_id, unit_id, full_data_set) - full_data_set.tech_groups.update( - {building_unlock.get_id(): building_unlock} - ) - full_data_set.building_unlocks.update( - {building_unlock.get_id(): building_unlock} - ) - break - - # Upgrades - elif effect.get_type() == 3: - source_unit_id = effect["attr_a"].value - target_unit_id = effect["attr_b"].value - unit = full_data_set.genie_units[source_unit_id] - unit_type = unit["unit_type"].value - - if unit_type == 70: - unit_upgrade = RoRUnitLineUpgrade(tech_id, - source_unit_id, - target_unit_id, - full_data_set) - full_data_set.tech_groups.update( - {unit_upgrade.get_id(): unit_upgrade} - ) - full_data_set.unit_upgrades.update( - {unit_upgrade.get_id(): unit_upgrade} - ) - break - - if unit_type == 80: - building_upgrade = RoRBuildingLineUpgrade(tech_id, - source_unit_id, - target_unit_id, - full_data_set) - full_data_set.tech_groups.update( - {building_upgrade.get_id(): building_upgrade} - ) - full_data_set.building_upgrades.update( - {building_upgrade.get_id(): building_upgrade} - ) - break - - else: - # Anything else must be a stat upgrade - stat_up = RoRStatUpgrade(tech_id, full_data_set) - full_data_set.tech_groups.update({stat_up.get_id(): stat_up}) - full_data_set.stat_upgrades.update({stat_up.get_id(): stat_up}) - - # Initiated techs are stored with buildings - genie_units = full_data_set.genie_units - - for genie_unit in genie_units.values(): - if not genie_unit.has_member("research_id"): - continue - - building_id = genie_unit["id0"].value - initiated_tech_id = genie_unit["research_id"].value - - if initiated_tech_id == -1: - continue - - if building_id not in full_data_set.building_lines.keys(): - # Skips upgraded buildings (which initiate the same techs) - continue - - initiated_tech = InitiatedTech(initiated_tech_id, building_id, full_data_set) - full_data_set.tech_groups.update({initiated_tech.get_id(): initiated_tech}) - full_data_set.initiated_techs.update({initiated_tech.get_id(): initiated_tech}) - - @staticmethod - def link_garrison(full_data_set: GenieObjectContainer) -> None: - """ - Link a garrison unit to the lines that are stored and vice versa. This is done - to provide quick access during conversion. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - unit_lines = full_data_set.unit_lines - - garrison_class_assignments = {} - - for unit_line in unit_lines.values(): - head_unit = unit_line.get_head_unit() - - unit_commands = head_unit["unit_commands"].value - for command in unit_commands: - command_type = command["type"].value - - if not command_type == 3: - continue - - class_id = command["class_id"].value - - if class_id in garrison_class_assignments: - garrison_class_assignments[class_id].append(unit_line) - - else: - garrison_class_assignments[class_id] = [unit_line] - - break - - for garrison in unit_lines.values(): - class_id = garrison.get_class_id() - - if class_id in garrison_class_assignments: - for line in garrison_class_assignments[class_id]: - garrison.garrison_entities.append(line) - line.garrison_locations.append(garrison) - - @staticmethod - def link_repairables(full_data_set: GenieObjectContainer) -> None: - """ - Set units/buildings as repairable - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - villager_groups = full_data_set.villager_groups - - repair_lines = {} - repair_lines.update(full_data_set.unit_lines) - repair_lines.update(full_data_set.building_lines) - - repair_classes = [] - for villager in villager_groups.values(): - repair_unit = villager.get_units_with_command(106)[0] - unit_commands = repair_unit["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - if type_id != 106: - continue - - class_id = command["class_id"].value - if class_id == -1: - # Buildings/Siege - repair_classes.append(3) - repair_classes.append(13) - - else: - repair_classes.append(class_id) - - for repair_line in repair_lines.values(): - if repair_line.get_class_id() in repair_classes: - repair_line.repairable = True + extract_genie_sounds = staticmethod(extract_genie_sounds) + extract_genie_units = staticmethod(extract_genie_units) + create_ambient_groups = staticmethod(create_ambient_groups) + create_entity_lines = staticmethod(create_entity_lines) + create_tech_groups = staticmethod(create_tech_groups) + create_variant_groups = staticmethod(create_variant_groups) + link_garrison = staticmethod(link_garrison) + link_repairables = staticmethod(link_repairables) From f9526b4b8891d3613af1ae8a2d809c37bbe395b6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 16:10:49 +0200 Subject: [PATCH 140/163] convert: Refactor RoRTechSubprocessor into separate files. --- .../processor/conversion/ror/CMakeLists.txt | 1 + .../conversion/ror/tech/CMakeLists.txt | 7 + .../processor/conversion/ror/tech/__init__.py | 5 + .../conversion/ror/tech/attribute_modify.py | 83 ++++++ .../conversion/ror/tech/resource_modify.py | 58 ++++ .../conversion/ror/tech/unit_upgrade.py | 165 +++++++++++ .../conversion/ror/tech/upgrade_funcs.py | 47 ++++ .../conversion/ror/tech_subprocessor.py | 259 +----------------- 8 files changed, 381 insertions(+), 244 deletions(-) create mode 100644 openage/convert/processor/conversion/ror/tech/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/tech/__init__.py create mode 100644 openage/convert/processor/conversion/ror/tech/attribute_modify.py create mode 100644 openage/convert/processor/conversion/ror/tech/resource_modify.py create mode 100644 openage/convert/processor/conversion/ror/tech/unit_upgrade.py create mode 100644 openage/convert/processor/conversion/ror/tech/upgrade_funcs.py diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index 61ca99acea..08ddc192a4 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -19,3 +19,4 @@ add_subdirectory(auxiliary) add_subdirectory(civ) add_subdirectory(main) add_subdirectory(nyan) +add_subdirectory(tech) diff --git a/openage/convert/processor/conversion/ror/tech/CMakeLists.txt b/openage/convert/processor/conversion/ror/tech/CMakeLists.txt new file mode 100644 index 0000000000..1ebd94a1c2 --- /dev/null +++ b/openage/convert/processor/conversion/ror/tech/CMakeLists.txt @@ -0,0 +1,7 @@ +add_py_modules( + __init__.py + attribute_modify.py + resource_modify.py + unit_upgrade.py + upgrade_funcs.py +) diff --git a/openage/convert/processor/conversion/ror/tech/__init__.py b/openage/convert/processor/conversion/ror/tech/__init__.py new file mode 100644 index 0000000000..ab3f958141 --- /dev/null +++ b/openage/convert/processor/conversion/ror/tech/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create nyan patches for techs in RoR. +""" diff --git a/openage/convert/processor/conversion/ror/tech/attribute_modify.py b/openage/convert/processor/conversion/ror/tech/attribute_modify.py new file mode 100644 index 0000000000..bce2060745 --- /dev/null +++ b/openage/convert/processor/conversion/ror/tech/attribute_modify.py @@ -0,0 +1,83 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying attributes of entities. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .upgrade_funcs import UPGRADE_ATTRIBUTE_FUNCS + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + from .....value_object.conversion.forward_ref import ForwardRef + + +def attribute_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying attributes of entities. + """ + patches = [] + dataset = converter_group.data + + effect_type = effect.get_type() + operator = None + if effect_type == 0: + operator = MemberOperator.ASSIGN + + elif effect_type == 4: + operator = MemberOperator.ADD + + elif effect_type == 5: + operator = MemberOperator.MULTIPLY + + else: + raise TypeError(f"Effect type {effect_type} is not a valid attribute effect") + + unit_id = effect["attr_a"].value + class_id = effect["attr_b"].value + attribute_type = effect["attr_c"].value + value = effect["attr_d"].value + + if attribute_type == -1: + return patches + + affected_entities = [] + if unit_id != -1: + entity_lines = {} + entity_lines.update(dataset.unit_lines) + entity_lines.update(dataset.building_lines) + entity_lines.update(dataset.ambient_groups) + + for line in entity_lines.values(): + if line.contains_entity(unit_id): + affected_entities.append(line) + + elif attribute_type == 19: + if line.is_projectile_shooter() and line.has_projectile(unit_id): + affected_entities.append(line) + + elif class_id != -1: + entity_lines = {} + entity_lines.update(dataset.unit_lines) + entity_lines.update(dataset.building_lines) + entity_lines.update(dataset.ambient_groups) + + for line in entity_lines.values(): + if line.get_class_id() == class_id: + affected_entities.append(line) + + else: + return patches + + upgrade_func = UPGRADE_ATTRIBUTE_FUNCS[attribute_type] + for affected_entity in affected_entities: + patches.extend(upgrade_func(converter_group, affected_entity, value, operator, team)) + + return patches diff --git a/openage/convert/processor/conversion/ror/tech/resource_modify.py b/openage/convert/processor/conversion/ror/tech/resource_modify.py new file mode 100644 index 0000000000..e82638a575 --- /dev/null +++ b/openage/convert/processor/conversion/ror/tech/resource_modify.py @@ -0,0 +1,58 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying resources. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .upgrade_funcs import UPGRADE_RESOURCE_FUNCS + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + from .....value_object.conversion.forward_ref import ForwardRef + + +def resource_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying resources. + """ + patches = [] + + effect_type = effect.get_type() + operator = None + if effect_type == 1: + mode = effect["attr_b"].value + + if mode == 0: + operator = MemberOperator.ASSIGN + + else: + operator = MemberOperator.ADD + + elif effect_type == 6: + operator = MemberOperator.MULTIPLY + + else: + raise TypeError(f"Effect type {effect_type} is not a valid resource effect") + + resource_id = effect["attr_a"].value + value = effect["attr_d"].value + + if resource_id in (-1, 6, 21, 30): + # -1 = invalid ID + # 6 = set current age (unused) + # 21 = tech count (unused) + # 30 = building limits (unused) + return patches + + upgrade_func = UPGRADE_RESOURCE_FUNCS[resource_id] + patches.extend(upgrade_func(converter_group, value, operator, team)) + + return patches diff --git a/openage/convert/processor/conversion/ror/tech/unit_upgrade.py b/openage/convert/processor/conversion/ror/tech/unit_upgrade.py new file mode 100644 index 0000000000..6722086878 --- /dev/null +++ b/openage/convert/processor/conversion/ror/tech/unit_upgrade.py @@ -0,0 +1,165 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for upgrading entities in a line. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, \ + GenieUnitLineGroup +from .....service.conversion import internal_name_lookups +from ...aoc.upgrade_ability_subprocessor import AoCUpgradeAbilitySubprocessor +from ..upgrade_ability_subprocessor import RoRUpgradeAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + from .....value_object.conversion.forward_ref import ForwardRef + + +def upgrade_unit_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject +) -> list[ForwardRef]: + """ + Creates the patches for upgrading entities in a line. + """ + patches = [] + tech_id = converter_group.get_id() + dataset = converter_group.data + + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + head_unit_id = effect["attr_a"].value + upgrade_target_id = effect["attr_b"].value + + if head_unit_id not in dataset.unit_ref.keys() or\ + upgrade_target_id not in dataset.unit_ref.keys(): + # Skip annexes or transform units + return patches + + line = dataset.unit_ref[head_unit_id] + upgrade_target_pos = line.get_unit_position(upgrade_target_id) + upgrade_source_pos = upgrade_target_pos - 1 + + upgrade_source = line.line[upgrade_source_pos] + upgrade_target = line.line[upgrade_target_pos] + tech_name = tech_lookup_dict[tech_id][0] + + diff = upgrade_source.diff(upgrade_target) + + patches.extend( + AoCUpgradeAbilitySubprocessor.death_ability( + converter_group, + line, + tech_name, + diff + ) + ) + patches.extend( + AoCUpgradeAbilitySubprocessor.despawn_ability( + converter_group, + line, + tech_name, + diff + ) + ) + patches.extend( + AoCUpgradeAbilitySubprocessor.idle_ability( + converter_group, + line, + tech_name, + diff + ) + ) + patches.extend( + AoCUpgradeAbilitySubprocessor.live_ability( + converter_group, + line, + tech_name, + diff + ) + ) + patches.extend( + AoCUpgradeAbilitySubprocessor.los_ability( + converter_group, + line, + tech_name, + diff + ) + ) + patches.extend( + AoCUpgradeAbilitySubprocessor.named_ability( + converter_group, + line, + tech_name, + diff + ) + ) + # patches.extend( + # AoCUpgradeAbilitySubprocessor.resistance_ability( + # converter_group, + # line, + # tech_name, + # diff + # ) + # ) + patches.extend( + AoCUpgradeAbilitySubprocessor.selectable_ability( + converter_group, + line, + tech_name, + diff + ) + ) + patches.extend( + AoCUpgradeAbilitySubprocessor.turn_ability( + converter_group, + line, + tech_name, diff + ) + ) + + if line.is_projectile_shooter(): + patches.extend( + RoRUpgradeAbilitySubprocessor.shoot_projectile_ability( + converter_group, + line, + tech_name, + 7, + diff + ) + ) + + elif line.is_melee() or line.is_ranged(): + if line.has_command(7): + # Attack + patches.extend( + AoCUpgradeAbilitySubprocessor.apply_discrete_effect_ability( + converter_group, + line, + tech_name, + 7, + line.is_ranged(), + diff + ) + ) + + if isinstance(line, GenieUnitLineGroup): + patches.extend( + AoCUpgradeAbilitySubprocessor.move_ability( + converter_group, + line, + tech_name, + diff + ) + ) + + if isinstance(line, GenieBuildingLineGroup): + # TODO: Damage percentages change + # patches.extend(AoCUpgradeAbilitySubprocessor.attribute_change_tracker_ability(converter_group, line, + # tech_name, diff)) + pass + + return patches diff --git a/openage/convert/processor/conversion/ror/tech/upgrade_funcs.py b/openage/convert/processor/conversion/ror/tech/upgrade_funcs.py new file mode 100644 index 0000000000..91b51535bd --- /dev/null +++ b/openage/convert/processor/conversion/ror/tech/upgrade_funcs.py @@ -0,0 +1,47 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Mappings of RoR upgrade IDs to their respective subprocessor functions. +""" + +from ...aoc.upgrade_attribute_subprocessor import AoCUpgradeAttributeSubprocessor +from ...aoc.upgrade_resource_subprocessor import AoCUpgradeResourceSubprocessor +from ..upgrade_attribute_subprocessor import RoRUpgradeAttributeSubprocessor +from ..upgrade_resource_subprocessor import RoRUpgradeResourceSubprocessor + +UPGRADE_ATTRIBUTE_FUNCS = { + 0: AoCUpgradeAttributeSubprocessor.hp_upgrade, + 1: AoCUpgradeAttributeSubprocessor.los_upgrade, + 2: AoCUpgradeAttributeSubprocessor.garrison_capacity_upgrade, + 3: AoCUpgradeAttributeSubprocessor.unit_size_x_upgrade, + 4: AoCUpgradeAttributeSubprocessor.unit_size_y_upgrade, + 5: AoCUpgradeAttributeSubprocessor.move_speed_upgrade, + 6: AoCUpgradeAttributeSubprocessor.rotation_speed_upgrade, + 8: AoCUpgradeAttributeSubprocessor.armor_upgrade, + 9: AoCUpgradeAttributeSubprocessor.attack_upgrade, + 10: AoCUpgradeAttributeSubprocessor.reload_time_upgrade, + 11: AoCUpgradeAttributeSubprocessor.accuracy_upgrade, + 12: AoCUpgradeAttributeSubprocessor.max_range_upgrade, + 13: AoCUpgradeAttributeSubprocessor.work_rate_upgrade, + 14: AoCUpgradeAttributeSubprocessor.carry_capacity_upgrade, + 16: AoCUpgradeAttributeSubprocessor.projectile_unit_upgrade, + 17: AoCUpgradeAttributeSubprocessor.graphics_angle_upgrade, + 18: AoCUpgradeAttributeSubprocessor.terrain_defense_upgrade, + 19: RoRUpgradeAttributeSubprocessor.ballistics_upgrade, + 100: AoCUpgradeAttributeSubprocessor.resource_cost_upgrade, + 101: RoRUpgradeAttributeSubprocessor.population_upgrade, +} + +UPGRADE_RESOURCE_FUNCS = { + 4: AoCUpgradeResourceSubprocessor.starting_population_space_upgrade, + 27: AoCUpgradeResourceSubprocessor.monk_conversion_upgrade, + 28: RoRUpgradeResourceSubprocessor.building_conversion_upgrade, + 32: AoCUpgradeResourceSubprocessor.bonus_population_upgrade, + 35: AoCUpgradeResourceSubprocessor.faith_recharge_rate_upgrade, + 36: AoCUpgradeResourceSubprocessor.farm_food_upgrade, + 46: AoCUpgradeResourceSubprocessor.tribute_inefficiency_upgrade, + 47: AoCUpgradeResourceSubprocessor.gather_gold_efficiency_upgrade, + 50: AoCUpgradeResourceSubprocessor.reveal_ally_upgrade, + 56: RoRUpgradeResourceSubprocessor.heal_bonus_upgrade, + 57: RoRUpgradeResourceSubprocessor.martyrdom_upgrade, +} diff --git a/openage/convert/processor/conversion/ror/tech_subprocessor.py b/openage/convert/processor/conversion/ror/tech_subprocessor.py index 224d844cf3..61c711d4a9 100644 --- a/openage/convert/processor/conversion/ror/tech_subprocessor.py +++ b/openage/convert/processor/conversion/ror/tech_subprocessor.py @@ -1,9 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-branches -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates patches for technologies. @@ -11,21 +6,14 @@ from __future__ import annotations import typing -from .....nyan.nyan_structs import MemberOperator -from ....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup, \ - GenieUnitLineGroup -from ....service.conversion import internal_name_lookups -from ..aoc.upgrade_ability_subprocessor import AoCUpgradeAbilitySubprocessor -from ..aoc.upgrade_attribute_subprocessor import AoCUpgradeAttributeSubprocessor -from ..aoc.upgrade_resource_subprocessor import AoCUpgradeResourceSubprocessor -from .upgrade_ability_subprocessor import RoRUpgradeAbilitySubprocessor -from .upgrade_attribute_subprocessor import RoRUpgradeAttributeSubprocessor -from .upgrade_resource_subprocessor import RoRUpgradeResourceSubprocessor +from .tech.attribute_modify import attribute_modify_effect +from .tech.resource_modify import resource_modify_effect +from .tech.unit_upgrade import upgrade_unit_effect +from .tech.upgrade_funcs import UPGRADE_ATTRIBUTE_FUNCS, UPGRADE_RESOURCE_FUNCS if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_effect import GenieEffectObject - from openage.convert.value_object.conversion.forward_ref import ForwardRef + from ....entity_object.conversion.converter_object import ConverterObjectGroup + from ....value_object.conversion.forward_ref import ForwardRef class RoRTechSubprocessor: @@ -33,42 +21,8 @@ class RoRTechSubprocessor: Creates raw API objects and patches for techs and civ setups in RoR. """ - upgrade_attribute_funcs = { - 0: AoCUpgradeAttributeSubprocessor.hp_upgrade, - 1: AoCUpgradeAttributeSubprocessor.los_upgrade, - 2: AoCUpgradeAttributeSubprocessor.garrison_capacity_upgrade, - 3: AoCUpgradeAttributeSubprocessor.unit_size_x_upgrade, - 4: AoCUpgradeAttributeSubprocessor.unit_size_y_upgrade, - 5: AoCUpgradeAttributeSubprocessor.move_speed_upgrade, - 6: AoCUpgradeAttributeSubprocessor.rotation_speed_upgrade, - 8: AoCUpgradeAttributeSubprocessor.armor_upgrade, - 9: AoCUpgradeAttributeSubprocessor.attack_upgrade, - 10: AoCUpgradeAttributeSubprocessor.reload_time_upgrade, - 11: AoCUpgradeAttributeSubprocessor.accuracy_upgrade, - 12: AoCUpgradeAttributeSubprocessor.max_range_upgrade, - 13: AoCUpgradeAttributeSubprocessor.work_rate_upgrade, - 14: AoCUpgradeAttributeSubprocessor.carry_capacity_upgrade, - 16: AoCUpgradeAttributeSubprocessor.projectile_unit_upgrade, - 17: AoCUpgradeAttributeSubprocessor.graphics_angle_upgrade, - 18: AoCUpgradeAttributeSubprocessor.terrain_defense_upgrade, - 19: RoRUpgradeAttributeSubprocessor.ballistics_upgrade, - 100: AoCUpgradeAttributeSubprocessor.resource_cost_upgrade, - 101: RoRUpgradeAttributeSubprocessor.population_upgrade, - } - - upgrade_resource_funcs = { - 4: AoCUpgradeResourceSubprocessor.starting_population_space_upgrade, - 27: AoCUpgradeResourceSubprocessor.monk_conversion_upgrade, - 28: RoRUpgradeResourceSubprocessor.building_conversion_upgrade, - 32: AoCUpgradeResourceSubprocessor.bonus_population_upgrade, - 35: AoCUpgradeResourceSubprocessor.faith_recharge_rate_upgrade, - 36: AoCUpgradeResourceSubprocessor.farm_food_upgrade, - 46: AoCUpgradeResourceSubprocessor.tribute_inefficiency_upgrade, - 47: AoCUpgradeResourceSubprocessor.gather_gold_efficiency_upgrade, - 50: AoCUpgradeResourceSubprocessor.reveal_ally_upgrade, - 56: RoRUpgradeResourceSubprocessor.heal_bonus_upgrade, - 57: RoRUpgradeResourceSubprocessor.martyrdom_upgrade, - } + upgrade_attribute_funcs = UPGRADE_ATTRIBUTE_FUNCS + upgrade_resource_funcs = UPGRADE_RESOURCE_FUNCS @classmethod def get_patches(cls, converter_group: ConverterObjectGroup) -> list[ForwardRef]: @@ -82,203 +36,20 @@ def get_patches(cls, converter_group: ConverterObjectGroup) -> list[ForwardRef]: type_id = effect.get_type() if type_id in (0, 4, 5): - patches.extend(cls.attribute_modify_effect(converter_group, effect)) + patches.extend(attribute_modify_effect(converter_group, effect)) elif type_id == 1: - patches.extend(cls.resource_modify_effect(converter_group, effect)) + patches.extend(resource_modify_effect(converter_group, effect)) elif type_id == 2: # Enabling/disabling units: Handled in creatable conditions pass elif type_id == 3: - patches.extend(cls.upgrade_unit_effect(converter_group, effect)) - - return patches - - @staticmethod - def attribute_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying attributes of entities. - """ - patches = [] - dataset = converter_group.data - - effect_type = effect.get_type() - operator = None - if effect_type == 0: - operator = MemberOperator.ASSIGN - - elif effect_type == 4: - operator = MemberOperator.ADD - - elif effect_type == 5: - operator = MemberOperator.MULTIPLY - - else: - raise TypeError(f"Effect type {effect_type} is not a valid attribute effect") - - unit_id = effect["attr_a"].value - class_id = effect["attr_b"].value - attribute_type = effect["attr_c"].value - value = effect["attr_d"].value - - if attribute_type == -1: - return patches - - affected_entities = [] - if unit_id != -1: - entity_lines = {} - entity_lines.update(dataset.unit_lines) - entity_lines.update(dataset.building_lines) - entity_lines.update(dataset.ambient_groups) - - for line in entity_lines.values(): - if line.contains_entity(unit_id): - affected_entities.append(line) - - elif attribute_type == 19: - if line.is_projectile_shooter() and line.has_projectile(unit_id): - affected_entities.append(line) - - elif class_id != -1: - entity_lines = {} - entity_lines.update(dataset.unit_lines) - entity_lines.update(dataset.building_lines) - entity_lines.update(dataset.ambient_groups) - - for line in entity_lines.values(): - if line.get_class_id() == class_id: - affected_entities.append(line) - - else: - return patches - - upgrade_func = RoRTechSubprocessor.upgrade_attribute_funcs[attribute_type] - for affected_entity in affected_entities: - patches.extend(upgrade_func(converter_group, affected_entity, value, operator, team)) - - return patches - - @staticmethod - def resource_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying resources. - """ - patches = [] - - effect_type = effect.get_type() - operator = None - if effect_type == 1: - mode = effect["attr_b"].value - - if mode == 0: - operator = MemberOperator.ASSIGN - - else: - operator = MemberOperator.ADD - - elif effect_type == 6: - operator = MemberOperator.MULTIPLY - - else: - raise TypeError(f"Effect type {effect_type} is not a valid resource effect") - - resource_id = effect["attr_a"].value - value = effect["attr_d"].value - - if resource_id in (-1, 6, 21, 30): - # -1 = invalid ID - # 6 = set current age (unused) - # 21 = tech count (unused) - # 30 = building limits (unused) - return patches - - upgrade_func = RoRTechSubprocessor.upgrade_resource_funcs[resource_id] - patches.extend(upgrade_func(converter_group, value, operator, team)) + patches.extend(upgrade_unit_effect(converter_group, effect)) return patches - @staticmethod - def upgrade_unit_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject - ) -> list[ForwardRef]: - """ - Creates the patches for upgrading entities in a line. - """ - patches = [] - tech_id = converter_group.get_id() - dataset = converter_group.data - - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - head_unit_id = effect["attr_a"].value - upgrade_target_id = effect["attr_b"].value - - if head_unit_id not in dataset.unit_ref.keys() or\ - upgrade_target_id not in dataset.unit_ref.keys(): - # Skip annexes or transform units - return patches - - line = dataset.unit_ref[head_unit_id] - upgrade_target_pos = line.get_unit_position(upgrade_target_id) - upgrade_source_pos = upgrade_target_pos - 1 - - upgrade_source = line.line[upgrade_source_pos] - upgrade_target = line.line[upgrade_target_pos] - tech_name = tech_lookup_dict[tech_id][0] - - diff = upgrade_source.diff(upgrade_target) - - patches.extend(AoCUpgradeAbilitySubprocessor.death_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.despawn_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.idle_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.live_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.los_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.named_ability( - converter_group, line, tech_name, diff)) - # patches.extend(AoCUpgradeAbilitySubprocessor.resistance_ability(converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.selectable_ability( - converter_group, line, tech_name, diff)) - patches.extend(AoCUpgradeAbilitySubprocessor.turn_ability( - converter_group, line, tech_name, diff)) - - if line.is_projectile_shooter(): - patches.extend(RoRUpgradeAbilitySubprocessor.shoot_projectile_ability(converter_group, line, - tech_name, - 7, diff)) - - elif line.is_melee() or line.is_ranged(): - if line.has_command(7): - # Attack - patches.extend(AoCUpgradeAbilitySubprocessor.apply_discrete_effect_ability(converter_group, - line, tech_name, - 7, - line.is_ranged(), - diff)) - - if isinstance(line, GenieUnitLineGroup): - patches.extend(AoCUpgradeAbilitySubprocessor.move_ability(converter_group, line, - tech_name, diff)) - - if isinstance(line, GenieBuildingLineGroup): - # TODO: Damage percentages change - # patches.extend(AoCUpgradeAbilitySubprocessor.attribute_change_tracker_ability(converter_group, line, - # tech_name, diff)) - pass - - return patches + attribute_modify_effect = staticmethod(attribute_modify_effect) + resource_modify_effect = staticmethod(resource_modify_effect) + upgrade_unit_effect = staticmethod(upgrade_unit_effect) From e2846fedc69cf6e2ac82687e6a5fd427d59bb7de Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 16:17:24 +0200 Subject: [PATCH 141/163] convert: Refactor RoRUpgradeAbilitySubprocessor into separate files. --- .../processor/conversion/ror/CMakeLists.txt | 1 + .../ror/upgrade_ability/CMakeLists.txt | 4 + .../ror/upgrade_ability/__init__.py | 5 + .../ror/upgrade_ability/shoot_projectile.py | 281 +++++++++++++++++ .../ror/upgrade_ability_subprocessor.py | 282 +----------------- 5 files changed, 293 insertions(+), 280 deletions(-) create mode 100644 openage/convert/processor/conversion/ror/upgrade_ability/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/upgrade_ability/__init__.py create mode 100644 openage/convert/processor/conversion/ror/upgrade_ability/shoot_projectile.py diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index 08ddc192a4..893dad9581 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -20,3 +20,4 @@ add_subdirectory(civ) add_subdirectory(main) add_subdirectory(nyan) add_subdirectory(tech) +add_subdirectory(upgrade_ability) diff --git a/openage/convert/processor/conversion/ror/upgrade_ability/CMakeLists.txt b/openage/convert/processor/conversion/ror/upgrade_ability/CMakeLists.txt new file mode 100644 index 0000000000..f93b80a880 --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_ability/CMakeLists.txt @@ -0,0 +1,4 @@ +add_py_modules( + __init__.py + shoot_projectile.py +) diff --git a/openage/convert/processor/conversion/ror/upgrade_ability/__init__.py b/openage/convert/processor/conversion/ror/upgrade_ability/__init__.py new file mode 100644 index 0000000000..a5f64c5c7e --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_ability/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create nyan patches for upgrading abilities of RoR entities. +""" diff --git a/openage/convert/processor/conversion/ror/upgrade_ability/shoot_projectile.py b/openage/convert/processor/conversion/ror/upgrade_ability/shoot_projectile.py new file mode 100644 index 0000000000..26573011fc --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_ability/shoot_projectile.py @@ -0,0 +1,281 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create patches for upgrading the ShootProjectile ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from .....value_object.read.value_members import NoDiffMember +from ...aoc.upgrade_ability_subprocessor import AoCUpgradeAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObject, \ + ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def shoot_projectile_ability( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + container_obj_ref: str, + command_id: int, + diff: ConverterObject +) -> list[ForwardRef]: + """ + Creates a patch for the Selectable ability of a line. + + :param converter_group: Group that gets the patch. + :param line: Unit/Building line that has the ability. + :param container_obj_ref: Reference of the raw API object the patch is nested in. + :param diff: A diff between two ConvertObject instances. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + tech_id = converter_group.get_id() + dataset = line.data + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + ability_name = command_lookup_dict[command_id][0] + + data_changed = False + + 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_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 + + # Nyan patch + patch_target_ref = f"{game_entity_name}.{ability_name}" + nyan_patch_name = f"Change{game_entity_name}{ability_name}" + wrapper, anim_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_", + [diff_animation_id] + ) + patches.append(anim_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if not isinstance(diff_comm_sound, NoDiffMember): + diff_comm_sound_id = diff_comm_sound.value + + # Nyan patch + patch_target_ref = f"{game_entity_name}.{ability_name}" + nyan_patch_name = f"Change{game_entity_name}{ability_name}" + wrapper, sound_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_command_sound_patch( + converter_group, + line, + patch_target_ref, + nyan_patch_name, + container_obj_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_", + [diff_comm_sound_id] + ) + patches.append(sound_patch_forward_ref) + + if isinstance(line, GenieBuildingLineGroup): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + wrapper.set_location(("data/game_entity/generic/" + f"{name_lookup_dict[head_unit_id][1]}/")) + wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") + + if data_changed: + patch_target_ref = f"{game_entity_name}.{ability_name}" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{ability_name}Wrapper" + 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): + # Store building upgrades next to their game entity definition, + # not in the Age up techs. + 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}" + nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_reload_time, NoDiffMember): + reload_time = diff_reload_time.value + + nyan_patch_raw_api_object.add_raw_patch_member("reload_time", + reload_time, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD) + + if not isinstance(diff_spawn_delay, NoDiffMember): + if not isinstance(diff_animation, NoDiffMember): + attack_graphic_id = diff_animation.value + + else: + attack_graphic_id = diff_animation.ref.value + + attack_graphic = dataset.genie_graphics[attack_graphic_id] + frame_rate = attack_graphic.get_frame_rate() + frame_delay = diff_spawn_delay.value + spawn_delay = frame_rate * frame_delay + + nyan_patch_raw_api_object.add_raw_patch_member("spawn_delay", + spawn_delay, + "engine.ability.type.ShootProjectile", + MemberOperator.ASSIGN) + + if not isinstance(diff_spawn_area_offsets, NoDiffMember): + diff_spawn_area_x = diff_spawn_area_offsets[0] + diff_spawn_area_y = diff_spawn_area_offsets[1] + diff_spawn_area_z = diff_spawn_area_offsets[2] + + if not isinstance(diff_spawn_area_x, NoDiffMember): + spawn_area_x = diff_spawn_area_x.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawning_area_offset_x", + spawn_area_x, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD + ) + + if not isinstance(diff_spawn_area_y, NoDiffMember): + spawn_area_y = diff_spawn_area_y.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawning_area_offset_y", + spawn_area_y, + "engine.ability.type.ShootProjectile", + MemberOperator.ADD + ) + + if not isinstance(diff_spawn_area_z, NoDiffMember): + spawn_area_z = diff_spawn_area_z.value + + nyan_patch_raw_api_object.add_raw_patch_member( + "spawning_area_offset_z", + spawn_area_z, + "engine.ability.type.ShootProjectile", + 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) + + return patches diff --git a/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py b/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py index 1d943ebd84..8a83e1c87e 100644 --- a/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py +++ b/openage/convert/processor/conversion/ror/upgrade_ability_subprocessor.py @@ -1,29 +1,9 @@ # 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 -# -# TODO: -# pylint: disable=line-too-long """ Creates upgrade patches for abilities. """ -from __future__ import annotations -import typing - -from .....nyan.nyan_structs import MemberOperator -from ....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ....value_object.read.value_members import NoDiffMember -from ..aoc.upgrade_ability_subprocessor import AoCUpgradeAbilitySubprocessor - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObject, \ - ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup +from .upgrade_ability.shoot_projectile import shoot_projectile_ability class RoRUpgradeAbilitySubprocessor: @@ -31,262 +11,4 @@ class RoRUpgradeAbilitySubprocessor: Creates raw API objects for ability upgrade effects in RoR. """ - @staticmethod - def shoot_projectile_ability( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - container_obj_ref: str, - command_id: int, - diff: ConverterObject - ) -> list[ForwardRef]: - """ - Creates a patch for the Selectable ability of a line. - - :param converter_group: Group that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param container_obj_ref: Reference of the raw API object the patch is nested in. - :type container_obj_ref: str - :param diff: A diff between two ConvertObject instances. - :type diff: ...dataformat.converter_object.ConverterObject - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - tech_id = converter_group.get_id() - dataset = line.data - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - ability_name = command_lookup_dict[command_id][0] - - data_changed = False - - 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_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 - - # Nyan patch - patch_target_ref = f"{game_entity_name}.{ability_name}" - nyan_patch_name = f"Change{game_entity_name}{ability_name}" - wrapper, anim_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_animation_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_", - [diff_animation_id] - ) - patches.append(anim_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if not isinstance(diff_comm_sound, NoDiffMember): - diff_comm_sound_id = diff_comm_sound.value - - # Nyan patch - patch_target_ref = f"{game_entity_name}.{ability_name}" - nyan_patch_name = f"Change{game_entity_name}{ability_name}" - wrapper, sound_patch_forward_ref = AoCUpgradeAbilitySubprocessor.create_command_sound_patch( - converter_group, - line, - patch_target_ref, - nyan_patch_name, - container_obj_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_", - [diff_comm_sound_id] - ) - patches.append(sound_patch_forward_ref) - - if isinstance(line, GenieBuildingLineGroup): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - wrapper.set_location(("data/game_entity/generic/" - f"{name_lookup_dict[head_unit_id][1]}/")) - wrapper.set_filename(f"{tech_lookup_dict[tech_id][1]}_upgrade") - - if data_changed: - patch_target_ref = f"{game_entity_name}.{ability_name}" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}{ability_name}Wrapper" - 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): - # Store building upgrades next to their game entity definition, - # not in the Age up techs. - 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}" - nyan_patch_ref = f"{container_obj_ref}.{wrapper_name}.{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_reload_time, NoDiffMember): - reload_time = diff_reload_time.value - - nyan_patch_raw_api_object.add_raw_patch_member("reload_time", - reload_time, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_spawn_delay, NoDiffMember): - if not isinstance(diff_animation, NoDiffMember): - attack_graphic_id = diff_animation.value - - else: - attack_graphic_id = diff_animation.ref.value - - attack_graphic = dataset.genie_graphics[attack_graphic_id] - frame_rate = attack_graphic.get_frame_rate() - frame_delay = diff_spawn_delay.value - spawn_delay = frame_rate * frame_delay - - nyan_patch_raw_api_object.add_raw_patch_member("spawn_delay", - spawn_delay, - "engine.ability.type.ShootProjectile", - MemberOperator.ASSIGN) - - if not isinstance(diff_spawn_area_offsets, NoDiffMember): - diff_spawn_area_x = diff_spawn_area_offsets[0] - diff_spawn_area_y = diff_spawn_area_offsets[1] - diff_spawn_area_z = diff_spawn_area_offsets[2] - - if not isinstance(diff_spawn_area_x, NoDiffMember): - spawn_area_x = diff_spawn_area_x.value - - nyan_patch_raw_api_object.add_raw_patch_member("spawning_area_offset_x", - spawn_area_x, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_spawn_area_y, NoDiffMember): - spawn_area_y = diff_spawn_area_y.value - - nyan_patch_raw_api_object.add_raw_patch_member("spawning_area_offset_y", - spawn_area_y, - "engine.ability.type.ShootProjectile", - MemberOperator.ADD) - - if not isinstance(diff_spawn_area_z, NoDiffMember): - spawn_area_z = diff_spawn_area_z.value - - nyan_patch_raw_api_object.add_raw_patch_member("spawning_area_offset_z", - spawn_area_z, - "engine.ability.type.ShootProjectile", - 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) - - return patches + shoot_projectile_ability = staticmethod(shoot_projectile_ability) From 3a7b88c677b6b7b2bbba0e0ed6ba769f1a924609 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 16:24:30 +0200 Subject: [PATCH 142/163] convert: Refactor RoRUpgradeAttributeSubprocessor into separate files. --- .../processor/conversion/ror/CMakeLists.txt | 1 + .../ror/upgrade_attribute/CMakeLists.txt | 5 + .../ror/upgrade_attribute/__init__.py | 5 + .../ror/upgrade_attribute/ballistics.py | 119 ++++++++++++++ .../ror/upgrade_attribute/population.py | 35 ++++ .../ror/upgrade_attribute_subprocessor.py | 149 +----------------- 6 files changed, 170 insertions(+), 144 deletions(-) create mode 100644 openage/convert/processor/conversion/ror/upgrade_attribute/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/upgrade_attribute/__init__.py create mode 100644 openage/convert/processor/conversion/ror/upgrade_attribute/ballistics.py create mode 100644 openage/convert/processor/conversion/ror/upgrade_attribute/population.py diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index 893dad9581..4f2d2095fa 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -21,3 +21,4 @@ add_subdirectory(main) add_subdirectory(nyan) add_subdirectory(tech) add_subdirectory(upgrade_ability) +add_subdirectory(upgrade_attribute) diff --git a/openage/convert/processor/conversion/ror/upgrade_attribute/CMakeLists.txt b/openage/convert/processor/conversion/ror/upgrade_attribute/CMakeLists.txt new file mode 100644 index 0000000000..6da173457b --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_attribute/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py + ballistics.py + population.py +) diff --git a/openage/convert/processor/conversion/ror/upgrade_attribute/__init__.py b/openage/convert/processor/conversion/ror/upgrade_attribute/__init__.py new file mode 100644 index 0000000000..00a56acaa3 --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_attribute/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create upgrade patches for attributes in RoR. +""" diff --git a/openage/convert/processor/conversion/ror/upgrade_attribute/ballistics.py b/openage/convert/processor/conversion/ror/upgrade_attribute/ballistics.py new file mode 100644 index 0000000000..ce4fafa482 --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_attribute/ballistics.py @@ -0,0 +1,119 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for ballistics in RoR. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from ......nyan.nyan_structs import MemberOperator + + +def ballistics_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the ballistics modify effect (ID: 19). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit = line.get_head_unit() + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + if value == 0: + target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.CurrentPosition"] + + elif value == 1: + target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.ExpectedPosition"] + + else: + raise ValueError(f"Invalid value for ballistics upgrade: {value}") + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + projectile_id0 = head_unit["projectile_id0"].value + if projectile_id0 > -1: + patch_target_ref = f"{game_entity_name}.ShootProjectile.Projectile0.Projectile" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}Projectile0TargetModeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}Projectile0TargetMode" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("target_mode", + target_mode, + "engine.ability.type.Projectile", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects[ + "util.patch.property.types.Team" + ].get_nyan_object() + properties = { + dataset.nyan_api_objects[ + "engine.util.patch.property.type.Diplomatic" + ]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/ror/upgrade_attribute/population.py b/openage/convert/processor/conversion/ror/upgrade_attribute/population.py new file mode 100644 index 0000000000..594131559e --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_attribute/population.py @@ -0,0 +1,35 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for population effects in RoR. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def population_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the population effect (ID: 101). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/ror/upgrade_attribute_subprocessor.py b/openage/convert/processor/conversion/ror/upgrade_attribute_subprocessor.py index fb2f9b7bb6..5eeb6584bc 100644 --- a/openage/convert/processor/conversion/ror/upgrade_attribute_subprocessor.py +++ b/openage/convert/processor/conversion/ror/upgrade_attribute_subprocessor.py @@ -1,25 +1,10 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-lines,too-many-statements,too-many-public-methods -# -# TODO: Remove when all methods are implemented -# pylint: disable=unused-argument,line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates upgrade patches for attribute modification effects in RoR. """ -from __future__ import annotations -import typing - -from ....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup - from openage.nyan.nyan_structs import MemberOperator +from .upgrade_attribute.ballistics import ballistics_upgrade +from .upgrade_attribute.population import population_upgrade class RoRUpgradeAttributeSubprocessor: @@ -27,129 +12,5 @@ class RoRUpgradeAttributeSubprocessor: Creates raw API objects for attribute upgrade effects in RoR. """ - @staticmethod - def ballistics_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the ballistics modify effect (ID: 19). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit = line.get_head_unit() - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - if value == 0: - target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.CurrentPosition"] - - elif value == 1: - target_mode = dataset.nyan_api_objects["engine.util.target_mode.type.ExpectedPosition"] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - projectile_id0 = head_unit["projectile_id0"].value - if projectile_id0 > -1: - patch_target_ref = f"{game_entity_name}.ShootProjectile.Projectile0.Projectile" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}Projectile0TargetModeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}Projectile0TargetMode" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("target_mode", - target_mode, - "engine.ability.type.Projectile", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def population_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the population effect (ID: 101). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches + ballistics_upgrade = staticmethod(ballistics_upgrade) + population_upgrade = staticmethod(population_upgrade) From 5ad1e51b41060b12d1c5269c3cd6737636a9d142 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 10 Jun 2025 16:33:07 +0200 Subject: [PATCH 143/163] convert: Refactor RoRUpgradeResourceSubprocessor into separate files. --- .../processor/conversion/ror/CMakeLists.txt | 1 + .../ror/upgrade_resource/CMakeLists.txt | 6 + .../ror/upgrade_resource/__init__.py | 5 + .../ror/upgrade_resource/convert_building.py | 114 ++++++++++++ .../ror/upgrade_resource/heal_bonus.py | 33 ++++ .../ror/upgrade_resource/martyrdom.py | 34 ++++ .../ror/upgrade_resource_subprocessor.py | 176 +----------------- 7 files changed, 200 insertions(+), 169 deletions(-) create mode 100644 openage/convert/processor/conversion/ror/upgrade_resource/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/ror/upgrade_resource/__init__.py create mode 100644 openage/convert/processor/conversion/ror/upgrade_resource/convert_building.py create mode 100644 openage/convert/processor/conversion/ror/upgrade_resource/heal_bonus.py create mode 100644 openage/convert/processor/conversion/ror/upgrade_resource/martyrdom.py diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index 4f2d2095fa..71bac0cfe2 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -22,3 +22,4 @@ add_subdirectory(nyan) add_subdirectory(tech) add_subdirectory(upgrade_ability) add_subdirectory(upgrade_attribute) +add_subdirectory(upgrade_resource) diff --git a/openage/convert/processor/conversion/ror/upgrade_resource/CMakeLists.txt b/openage/convert/processor/conversion/ror/upgrade_resource/CMakeLists.txt new file mode 100644 index 0000000000..2c2b1bd350 --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_resource/CMakeLists.txt @@ -0,0 +1,6 @@ +add_py_modules( + __init__.py + convert_building.py + heal_bonus.py + martyrdom.py +) diff --git a/openage/convert/processor/conversion/ror/upgrade_resource/__init__.py b/openage/convert/processor/conversion/ror/upgrade_resource/__init__.py new file mode 100644 index 0000000000..754fd69008 --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_resource/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create upgrade patches for civ resources in RoR. +""" diff --git a/openage/convert/processor/conversion/ror/upgrade_resource/convert_building.py b/openage/convert/processor/conversion/ror/upgrade_resource/convert_building.py new file mode 100644 index 0000000000..58b9540ae0 --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_resource/convert_building.py @@ -0,0 +1,114 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for building conversion in RoR. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + + +def convert_building_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the building conversion effect (ID: 28). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + monk_id = 125 + dataset = converter_group.data + line = dataset.unit_lines[monk_id] + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[monk_id][0] + + patch_target_ref = f"{game_entity_name}.Convert" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Building conversion + + # Wrapper + wrapper_name = "EnableBuildingConversionWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = "EnableBuildingConversion" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + # New allowed types + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object() + ] + nyan_patch_raw_api_object.add_raw_patch_member("allowed_types", + allowed_types, + "engine.ability.type.ApplyDiscreteEffect", + MemberOperator.ADD) + + # Blacklisted buildings + tc_line = dataset.building_lines[109] + farm_line = dataset.building_lines[50] + monastery_line = dataset.building_lines[104] + wonder_line = dataset.building_lines[276] + + blacklisted_forward_refs = [ForwardRef(tc_line, "TownCenter"), + ForwardRef(farm_line, "Farm"), + ForwardRef(monastery_line, "Temple"), + ForwardRef(wonder_line, "Wonder"), + ] + nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", + blacklisted_forward_refs, + "engine.ability.type.ApplyDiscreteEffect", + 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) + + return patches diff --git a/openage/convert/processor/conversion/ror/upgrade_resource/heal_bonus.py b/openage/convert/processor/conversion/ror/upgrade_resource/heal_bonus.py new file mode 100644 index 0000000000..1ad5d1b093 --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_resource/heal_bonus.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for heal bonuses in RoR. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def heal_bonus_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the AoE1 heal bonus effect (ID: 56). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/ror/upgrade_resource/martyrdom.py b/openage/convert/processor/conversion/ror/upgrade_resource/martyrdom.py new file mode 100644 index 0000000000..f4d41c114d --- /dev/null +++ b/openage/convert/processor/conversion/ror/upgrade_resource/martyrdom.py @@ -0,0 +1,34 @@ + +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the martyrdom effect in RoR. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def martyrdom_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the martyrdom effect (ID: 57). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/ror/upgrade_resource_subprocessor.py b/openage/convert/processor/conversion/ror/upgrade_resource_subprocessor.py index 98d4843ed7..2dc94f19d3 100644 --- a/openage/convert/processor/conversion/ror/upgrade_resource_subprocessor.py +++ b/openage/convert/processor/conversion/ror/upgrade_resource_subprocessor.py @@ -1,25 +1,11 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-lines,too-many-statements,too-many-public-methods -# -# TODO: Remove when all methods are implemented -# pylint: disable=unused-argument +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates upgrade patches for resource modification effects in RoR. """ -from __future__ import annotations -import typing - -from .....nyan.nyan_structs import MemberOperator -from ....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.nyan.nyan_structs import MemberOperator +from .upgrade_resource.convert_building import convert_building_upgrade +from .upgrade_resource.heal_bonus import heal_bonus_upgrade +from .upgrade_resource.martyrdom import martyrdom_upgrade class RoRUpgradeResourceSubprocessor: @@ -27,154 +13,6 @@ class RoRUpgradeResourceSubprocessor: Creates raw API objects for resource upgrade effects in RoR. """ - @staticmethod - def building_conversion_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the building conversion effect (ID: 28). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - monk_id = 125 - dataset = converter_group.data - line = dataset.unit_lines[monk_id] - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[monk_id][0] - - patch_target_ref = f"{game_entity_name}.Convert" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Building conversion - - # Wrapper - wrapper_name = "EnableBuildingConversionWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = "EnableBuildingConversion" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - # New allowed types - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object() - ] - nyan_patch_raw_api_object.add_raw_patch_member("allowed_types", - allowed_types, - "engine.ability.type.ApplyDiscreteEffect", - MemberOperator.ADD) - - # Blacklisted buildings - tc_line = dataset.building_lines[109] - farm_line = dataset.building_lines[50] - monastery_line = dataset.building_lines[104] - wonder_line = dataset.building_lines[276] - - blacklisted_forward_refs = [ForwardRef(tc_line, "TownCenter"), - ForwardRef(farm_line, "Farm"), - ForwardRef(monastery_line, "Temple"), - ForwardRef(wonder_line, "Wonder"), - ] - nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", - blacklisted_forward_refs, - "engine.ability.type.ApplyDiscreteEffect", - 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) - - return patches - - @staticmethod - def heal_bonus_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the AoE1 heal bonus effect (ID: 56). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def martyrdom_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the martyrdom effect (ID: 57). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches + building_conversion_upgrade = staticmethod(convert_building_upgrade) + heal_bonus_upgrade = staticmethod(heal_bonus_upgrade) + martyrdom_upgrade = staticmethod(martyrdom_upgrade) From 5c42e41c55560caed3e99b74b3abba4874671c2a Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 14:02:42 +0200 Subject: [PATCH 144/163] convert: Fix missing garrison empty condition in SWGB. --- .../convert/processor/conversion/swgbcc/pregen_subprocessor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py index fb96a60698..7425891ffa 100644 --- a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py @@ -52,6 +52,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: AoCPregenSubprocessor.generate_path_types(full_data_set, pregen_converter_group) cls.generate_resources(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_death_condition(full_data_set, pregen_converter_group) + AoCPregenSubprocessor.generate_garrison_empty_condition(full_data_set, pregen_converter_group) pregen_nyan_objects = full_data_set.pregen_nyan_objects # Create nyan objects from the raw API objects From 3b7ba6c4e76e308147af9fc74c0123f5df1a5808 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 14:03:42 +0200 Subject: [PATCH 145/163] convert: Refactor SWGBCCAbilitySubprocessor into separate files. --- .../conversion/swgbcc/CMakeLists.txt | 2 + .../conversion/swgbcc/ability/CMakeLists.txt | 28 + .../conversion/swgbcc/ability/__init__.py | 5 + .../swgbcc/ability/active_transform_to.py | 27 + .../swgbcc/ability/apply_continuous_effect.py | 32 + .../swgbcc/ability/apply_discrete_effect.py | 338 +++ .../ability/attribute_change_tracker.py | 137 ++ .../conversion/swgbcc/ability/collision.py | 27 + .../swgbcc/ability/constructable.py | 27 + .../conversion/swgbcc/ability/death.py | 27 + .../swgbcc/ability/exchange_resources.py | 78 + .../conversion/swgbcc/ability/gather.py | 259 +++ .../conversion/swgbcc/ability/harvestable.py | 402 ++++ .../conversion/swgbcc/ability/idle.py | 29 + .../swgbcc/ability/line_of_sight.py | 27 + .../conversion/swgbcc/ability/live.py | 29 + .../conversion/swgbcc/ability/move.py | 27 + .../conversion/swgbcc/ability/named.py | 27 + .../swgbcc/ability/provide_contingent.py | 27 + .../swgbcc/ability/regenerate_attribute.py | 93 + .../swgbcc/ability/resource_storage.py | 265 +++ .../conversion/swgbcc/ability/restock.py | 27 + .../conversion/swgbcc/ability/selectable.py | 29 + .../swgbcc/ability/send_back_to_task.py | 55 + .../swgbcc/ability/shoot_projectile.py | 27 + .../conversion/swgbcc/ability/trade.py | 76 + .../conversion/swgbcc/ability/trade_post.py | 77 + .../conversion/swgbcc/ability/turn.py | 27 + .../conversion/swgbcc/ability_subprocessor.py | 1938 +---------------- 29 files changed, 2281 insertions(+), 1888 deletions(-) create mode 100644 openage/convert/processor/conversion/swgbcc/ability/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/ability/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/active_transform_to.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/apply_continuous_effect.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/apply_discrete_effect.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/attribute_change_tracker.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/collision.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/constructable.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/death.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/exchange_resources.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/gather.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/harvestable.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/idle.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/line_of_sight.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/live.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/move.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/named.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/provide_contingent.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/regenerate_attribute.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/resource_storage.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/restock.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/selectable.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/send_back_to_task.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/shoot_projectile.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/trade.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/trade_post.py create mode 100644 openage/convert/processor/conversion/swgbcc/ability/turn.py diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index 7fe5268a7e..ea016e251f 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -11,3 +11,5 @@ add_py_modules( upgrade_attribute_subprocessor.py upgrade_resource_subprocessor.py ) + +add_subdirectory(ability) diff --git a/openage/convert/processor/conversion/swgbcc/ability/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/ability/CMakeLists.txt new file mode 100644 index 0000000000..1e37682f49 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/CMakeLists.txt @@ -0,0 +1,28 @@ +add_py_modules( + __init__.py + active_transform_to.py + apply_continuous_effect.py + apply_discrete_effect.py + attribute_change_tracker.py + collision.py + constructable.py + death.py + exchange_resources.py + gather.py + harvestable.py + idle.py + line_of_sight.py + live.py + move.py + named.py + provide_contingent.py + regenerate_attribute.py + resource_storage.py + restock.py + selectable.py + send_back_to_task.py + shoot_projectile.py + trade_post.py + trade.py + turn.py +) diff --git a/openage/convert/processor/conversion/swgbcc/ability/__init__.py b/openage/convert/processor/conversion/swgbcc/ability/__init__.py new file mode 100644 index 0000000000..614701233a --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Derives and adds abilities to game entities created from lines. +""" diff --git a/openage/convert/processor/conversion/swgbcc/ability/active_transform_to.py b/openage/convert/processor/conversion/swgbcc/ability/active_transform_to.py new file mode 100644 index 0000000000..3d342b049f --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/active_transform_to.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ActiveTransformTo ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def active_transform_to_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ActiveTransformTo ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.active_transform_to_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/apply_continuous_effect.py b/openage/convert/processor/conversion/swgbcc/ability/apply_continuous_effect.py new file mode 100644 index 0000000000..5eaa5803fe --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/apply_continuous_effect.py @@ -0,0 +1,32 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ApplyContinuousEffect ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def apply_continuous_effect_ability( + line: GenieGameEntityGroup, + command_id: int, + ranged: bool = False +) -> ForwardRef: + """ + Adds the ApplyContinuousEffect ability to a line that is used to make entities die. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.apply_continuous_effect_ability( + line, command_id, ranged) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/apply_discrete_effect.py b/openage/convert/processor/conversion/swgbcc/ability/apply_discrete_effect.py new file mode 100644 index 0000000000..5eea58677a --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/apply_discrete_effect.py @@ -0,0 +1,338 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ApplyDiscreteEffect ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ...aoc.effect_subprocessor import AoCEffectSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def apply_discrete_effect_ability( + line: GenieGameEntityGroup, + command_id: int, + ranged: bool = False, + projectile: int = -1 +) -> ForwardRef: + """ + Adds the ApplyDiscreteEffect ability to a line that is used to make entities die. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + if isinstance(line, GenieVillagerGroup): + current_unit = line.get_units_with_command(command_id)[0] + current_unit_id = current_unit["id0"].value + + else: + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + + head_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) + gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + ability_name = command_lookup_dict[command_id][0] + 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_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + ability_animation_id = current_unit["attack_sprite_id"].value + + else: + ability_ref = f"{game_entity_name}.ShootProjectile.Projectile{str(projectile)}.{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_location = ForwardRef(line, + (f"{game_entity_name}.ShootProjectile." + f"Projectile{projectile}")) + ability_raw_api_object.set_location(ability_location) + + ability_animation_id = -1 + + # Ability properties + properties = {} + + # Animated + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = AoCAbilitySubprocessor.create_animation( + line, + ability_animation_id, + property_ref, + ability_name, + f"{command_lookup_dict[command_id][1]}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Create custom civ graphics + handled_graphics_set_ids = set() + for civ_group in dataset.civ_groups.values(): + civ = civ_group.civ + civ_id = civ_group.get_id() + + # Only proceed if the civ stores the unit in the line + if current_unit_id not in civ["units"].value.keys(): + continue + + civ_animation_id = civ["units"][current_unit_id]["attack_sprite_id"].value + + if civ_animation_id != ability_animation_id: + # Find the corresponding graphics set + graphics_set_id = -1 + for set_id, items in gset_lookup_dict.items(): + if civ_id in items[0]: + graphics_set_id = set_id + break + + # Check if the object for the animation has been created before + obj_exists = graphics_set_id in handled_graphics_set_ids + if not obj_exists: + handled_graphics_set_ids.add(graphics_set_id) + + obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}{ability_name}" + filename_prefix = (f"{command_lookup_dict[command_id][1]}_" + f"{gset_lookup_dict[graphics_set_id][2]}_") + AoCAbilitySubprocessor.create_civ_animation(line, + civ_group, + civ_animation_id, + property_ref, + obj_prefix, + filename_prefix, + obj_exists) + + # Command Sound + if projectile == -1: + ability_comm_sound_id = current_unit["command_sound_id"].value + + else: + ability_comm_sound_id = -1 + + if ability_comm_sound_id > -1: + property_ref = f"{ability_ref}.CommandSound" + property_raw_api_object = RawAPIObject(property_ref, + "CommandSound", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + sounds_set = [] + + if projectile == -1: + sound_obj_prefix = ability_name + + else: + sound_obj_prefix = "ProjectileAttack" + + sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, + ability_comm_sound_id, + property_ref, + sound_obj_prefix, + "command_") + sounds_set.append(sound_forward_ref) + property_raw_api_object.add_raw_member("sounds", sounds_set, + "engine.ability.property.type.CommandSound") + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + 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 + 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 + 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" + batch_raw_api_object = RawAPIObject(batch_ref, "Batch", dataset.nyan_api_objects) + batch_raw_api_object.add_raw_parent("engine.util.effect_batch.type.UnorderedBatch") + batch_location = ForwardRef(line, ability_ref) + batch_raw_api_object.set_location(batch_location) + + line.add_raw_api_object(batch_raw_api_object) + + # Effects + effects = [] + if command_id == 7: + # Attack + if projectile != 1: + effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref) + + else: + effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref, projectile=1) + + elif command_id == 104: + # Convert + effects = AoCEffectSubprocessor.get_convert_effects(line, batch_ref) + + batch_raw_api_object.add_raw_member("effects", + effects, + "engine.util.effect_batch.EffectBatch") + + batch_forward_ref = ForwardRef(line, batch_ref) + ability_raw_api_object.add_raw_member("batches", + [batch_forward_ref], + "engine.ability.type.ApplyDiscreteEffect") + + # Reload time + if projectile == -1: + reload_time = current_unit["attack_speed"].value + + else: + reload_time = 0 + + ability_raw_api_object.add_raw_member("reload_time", + reload_time, + "engine.ability.type.ApplyDiscreteEffect") + + # Application delay + if projectile == -1: + attack_graphic_id = current_unit["attack_sprite_id"].value + attack_graphic = dataset.genie_graphics[attack_graphic_id] + frame_rate = attack_graphic.get_frame_rate() + frame_delay = current_unit["frame_delay"].value + application_delay = frame_rate * frame_delay + + else: + application_delay = 0 + + ability_raw_api_object.add_raw_member("application_delay", + application_delay, + "engine.ability.type.ApplyDiscreteEffect") + + # Allowed types (all buildings/units) + if command_id == 104: + # Convert + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object() + ] + + else: + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object() + ] + + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.ApplyDiscreteEffect") + + if command_id == 104: + # Convert + force_master_line = dataset.unit_lines[115] + force_line = dataset.unit_lines[180] + artillery_line = dataset.unit_lines[691] + anti_air_line = dataset.unit_lines[702] + pummel_line = dataset.unit_lines[713] + + blacklisted_entities = [ForwardRef(force_master_line, "ForceMaster"), + ForwardRef(force_line, "ForceKnight"), + ForwardRef(artillery_line, "Artillery"), + ForwardRef(anti_air_line, "AntiAirMobile"), + ForwardRef(pummel_line, "Pummel")] + + else: + blacklisted_entities = [] + + ability_raw_api_object.add_raw_member("blacklisted_entities", + blacklisted_entities, + "engine.ability.type.ApplyDiscreteEffect") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/attribute_change_tracker.py b/openage/convert/processor/conversion/swgbcc/ability/attribute_change_tracker.py new file mode 100644 index 0000000000..52c148e1c4 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/attribute_change_tracker.py @@ -0,0 +1,137 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the AttributeChangeTracker ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieStackBuildingGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def attribute_change_tracker_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the AttributeChangeTracker ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + if isinstance(line, GenieStackBuildingGroup): + current_unit = line.get_stack_unit() + current_unit_id = line.get_stack_unit_id() + + else: + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + + dataset = line.data + api_objects = dataset.nyan_api_objects + + 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}.AttributeChangeTracker" + ability_raw_api_object = RawAPIObject( + ability_ref, "AttributeChangeTracker", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.AttributeChangeTracker") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Attribute + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + ability_raw_api_object.add_raw_member("attribute", + attribute, + "engine.ability.type.AttributeChangeTracker") + + # Change progress + damage_graphics = current_unit["damage_graphics"].value + progress_forward_refs = [] + + # Damage graphics are ordered ascending, so we start from 0 + interval_left_bound = 0 + for damage_graphic_member in damage_graphics: + interval_right_bound = damage_graphic_member["damage_percent"].value + progress_ref = f"{game_entity_name}.AttributeChangeTracker.ChangeProgress{interval_right_bound}" + progress_raw_api_object = RawAPIObject(progress_ref, + f"ChangeProgress{interval_right_bound}", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member("type", + api_objects["engine.util.progress_type.type.AttributeChange"], + "engine.util.progress.Progress") + + # Interval + progress_raw_api_object.add_raw_member("left_boundary", + interval_left_bound, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + interval_right_bound, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # AnimationOverlay property + # ===================================================================================== + progress_animation_id = damage_graphic_member["graphic_id"].value + if progress_animation_id > -1: + property_ref = f"{progress_ref}.AnimationOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "AnimationOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.AnimationOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Animation + animations_set = [] + animation_forward_ref = AoCAbilitySubprocessor.create_animation( + line, + progress_animation_id, + property_ref, + "Idle", + f"idle_damage_override_{interval_right_bound}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("overlays", + animations_set, + "engine.util.progress.property.type.AnimationOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.AnimationOverlay"]: property_forward_ref + }) + + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + interval_left_bound = interval_right_bound + + ability_raw_api_object.add_raw_member("change_progress", + progress_forward_refs, + "engine.ability.type.AttributeChangeTracker") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/collision.py b/openage/convert/processor/conversion/swgbcc/ability/collision.py new file mode 100644 index 0000000000..c19f6f034e --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/collision.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Collision ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def collision_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Collision ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.collision_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/constructable.py b/openage/convert/processor/conversion/swgbcc/ability/constructable.py new file mode 100644 index 0000000000..2b9ab887c5 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/constructable.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Constructable ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def constructable_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Constructable ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.constructable_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/death.py b/openage/convert/processor/conversion/swgbcc/ability/death.py new file mode 100644 index 0000000000..1a619770e4 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/death.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for death via the PassiveTransformTo ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def death_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Death ability to a line that is used to make entities die. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.death_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/exchange_resources.py b/openage/convert/processor/conversion/swgbcc/ability/exchange_resources.py new file mode 100644 index 0000000000..2994cac616 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/exchange_resources.py @@ -0,0 +1,78 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ExchangeContainer ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def exchange_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ExchangeResources ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + 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] + + resource_names = ["Food", "Carbon", "Ore"] + + abilities = [] + for resource_name in resource_names: + ability_name = f"MarketExchange{resource_name}" + 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("engine.ability.type.ExchangeResources") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Resource that is exchanged (resource A) + resource_a = dataset.pregen_nyan_objects[f"util.resource.types.{resource_name}"].get_nyan_object( + ) + ability_raw_api_object.add_raw_member("resource_a", + resource_a, + "engine.ability.type.ExchangeResources") + + # Resource that is exchanged for (resource B) + resource_b = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() + ability_raw_api_object.add_raw_member("resource_b", + resource_b, + "engine.ability.type.ExchangeResources") + + # Exchange rate + exchange_rate_ref = f"util.resource.market_trading.Market{resource_name}ExchangeRate" + exchange_rate = dataset.pregen_nyan_objects[exchange_rate_ref].get_nyan_object() + ability_raw_api_object.add_raw_member("exchange_rate", + exchange_rate, + "engine.ability.type.ExchangeResources") + + # Exchange modes + buy_exchange_ref = "util.resource.market_trading.MarketBuyExchangeMode" + sell_exchange_ref = "util.resource.market_trading.MarketSellExchangeMode" + exchange_modes = [ + dataset.pregen_nyan_objects[buy_exchange_ref].get_nyan_object(), + dataset.pregen_nyan_objects[sell_exchange_ref].get_nyan_object(), + ] + ability_raw_api_object.add_raw_member("exchange_modes", + exchange_modes, + "engine.ability.type.ExchangeResources") + + line.add_raw_api_object(ability_raw_api_object) + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + abilities.append(ability_forward_ref) + + return abilities diff --git a/openage/convert/processor/conversion/swgbcc/ability/gather.py b/openage/convert/processor/conversion/swgbcc/ability/gather.py new file mode 100644 index 0000000000..9f26f39e55 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/gather.py @@ -0,0 +1,259 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Gather ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from ......util.ordered_set import OrderedSet +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def gather_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Gather abilities to a line. Unlike the other methods, this + creates multiple abilities. + + :param line: Unit/Building line that gets the ability. + :returns: The forward references for the abilities. + """ + if isinstance(line, GenieVillagerGroup): + gatherers = line.variants[0].line + + else: + gatherers = [line.line[0]] + + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + abilities = [] + for gatherer in gatherers: + unit_commands = gatherer["unit_commands"].value + resource = None + ability_animation_id = -1 + harvestable_class_ids = OrderedSet() + harvestable_unit_ids = OrderedSet() + + for command in unit_commands: + # Find a gather ability. It doesn't matter which one because + # they should all produce the same resource for one genie unit. + type_id = command["type"].value + + if type_id not in (5, 110): + continue + + target_class_id = command["class_id"].value + if target_class_id > -1: + harvestable_class_ids.add(target_class_id) + + target_unit_id = command["unit_id"].value + if target_unit_id > -1: + harvestable_unit_ids.add(target_unit_id) + + resource_id = command["resource_out"].value + + # If resource_out is not specified, the gatherer harvests resource_in + if resource_id == -1: + resource_id = command["resource_in"].value + + if resource_id == 0: + resource = dataset.pregen_nyan_objects[ + "util.resource.types.Food" + ].get_nyan_object() + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects[ + "util.resource.types.Carbon" + ].get_nyan_object() + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects[ + "util.resource.types.Ore" + ].get_nyan_object() + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects[ + "util.resource.types.Nova" + ].get_nyan_object() + + else: + continue + + if type_id == 110: + ability_animation_id = command["work_sprite_id"].value + + else: + ability_animation_id = command["proceed_sprite_id"].value + + # Look for the harvestable groups that match the class IDs and unit IDs + check_groups = [] + check_groups.extend(dataset.unit_lines.values()) + check_groups.extend(dataset.building_lines.values()) + check_groups.extend(dataset.ambient_groups.values()) + + harvestable_groups = [] + for group in check_groups: + if not group.is_harvestable(): + continue + + if group.get_class_id() in harvestable_class_ids: + harvestable_groups.append(group) + continue + + for unit_id in harvestable_unit_ids: + if group.contains_entity(unit_id): + harvestable_groups.append(group) + + if len(harvestable_groups) == 0: + # If no matching groups are found, then we don't + # need to create an ability. + continue + + gatherer_unit_id = gatherer.get_id() + if gatherer_unit_id not in gather_lookup_dict: + # Skips hunting wolves + continue + + ability_name = gather_lookup_dict[gatherer_unit_id][0] + + 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("engine.ability.type.Gather") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + line.add_raw_api_object(ability_raw_api_object) + + # Ability properties + properties = {} + + # Animation + if ability_animation_id > -1: + property_ref = f"{ability_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + animations_set = [] + animation_forward_ref = AoCAbilitySubprocessor.create_animation( + line, + ability_animation_id, + property_ref, + ability_name, + f"{gather_lookup_dict[gatherer_unit_id][1]}_" + ) + animations_set.append(animation_forward_ref) + property_raw_api_object.add_raw_member("animations", animations_set, + "engine.ability.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Animated"]: property_forward_ref + }) + + # Diplomacy settings + property_ref = f"{ability_ref}.Diplomatic" + property_raw_api_object = RawAPIObject(property_ref, + "Diplomatic", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") + property_location = ForwardRef(line, ability_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + diplomatic_stances = [ + dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] + property_raw_api_object.add_raw_member("stances", diplomatic_stances, + "engine.ability.property.type.Diplomatic") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref + }) + + ability_raw_api_object.add_raw_member("properties", + properties, + "engine.ability.Ability") + + # Auto resume + ability_raw_api_object.add_raw_member("auto_resume", + True, + "engine.ability.type.Gather") + + # search range + ability_raw_api_object.add_raw_member("resume_search_range", + MemberSpecialValue.NYAN_INF, + "engine.ability.type.Gather") + + # Gather rate + rate_name = f"{game_entity_name}.{ability_name}.GatherRate" + rate_raw_api_object = RawAPIObject(rate_name, "GatherRate", dataset.nyan_api_objects) + rate_raw_api_object.add_raw_parent("engine.util.resource.ResourceRate") + rate_location = ForwardRef(line, ability_ref) + rate_raw_api_object.set_location(rate_location) + + rate_raw_api_object.add_raw_member( + "type", resource, "engine.util.resource.ResourceRate") + + gather_rate = gatherer["work_rate"].value + rate_raw_api_object.add_raw_member( + "rate", gather_rate, "engine.util.resource.ResourceRate") + + line.add_raw_api_object(rate_raw_api_object) + + rate_forward_ref = ForwardRef(line, rate_name) + ability_raw_api_object.add_raw_member("gather_rate", + rate_forward_ref, + "engine.ability.type.Gather") + + # Resource container + container_ref = (f"{game_entity_name}.ResourceStorage." + f"{gather_lookup_dict[gatherer_unit_id][0]}Container") + container_forward_ref = ForwardRef(line, container_ref) + ability_raw_api_object.add_raw_member("container", + container_forward_ref, + "engine.ability.type.Gather") + + # Targets (resource spots) + entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) + spot_forward_refs = [] + for group in harvestable_groups: + group_id = group.get_head_unit_id() + group_name = entity_lookups[group_id][0] + + spot_forward_ref = ForwardRef( + group, + f"{group_name}.Harvestable.{group_name}ResourceSpot" + ) + spot_forward_refs.append(spot_forward_ref) + + ability_raw_api_object.add_raw_member("targets", + spot_forward_refs, + "engine.ability.type.Gather") + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + abilities.append(ability_forward_ref) + + return abilities diff --git a/openage/convert/processor/conversion/swgbcc/ability/harvestable.py b/openage/convert/processor/conversion/swgbcc/ability/harvestable.py new file mode 100644 index 0000000000..4f280223d8 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/harvestable.py @@ -0,0 +1,402 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Harvestable ability. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def harvestable_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Harvestable ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + 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}.Harvestable" + ability_raw_api_object = RawAPIObject(ability_ref, "Harvestable", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Harvestable") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Resource spot + resource_storage = current_unit["resource_storage"].value + + for storage in resource_storage: + resource_id = storage["type"].value + + # IDs 15, 16, 17 are other types of food (meat, berries, fish) + if resource_id in (0, 15, 16, 17): + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object( + ) + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object() + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() + + else: + continue + + spot_name = f"{game_entity_name}.Harvestable.{game_entity_name}ResourceSpot" + spot_raw_api_object = RawAPIObject(spot_name, + f"{game_entity_name}ResourceSpot", + dataset.nyan_api_objects) + spot_raw_api_object.add_raw_parent("engine.util.resource_spot.ResourceSpot") + spot_location = ForwardRef(line, ability_ref) + spot_raw_api_object.set_location(spot_location) + + # Type + spot_raw_api_object.add_raw_member("resource", + resource, + "engine.util.resource_spot.ResourceSpot") + + # Start amount (equals max amount) + if line.get_id() == 50: + # Farm food amount (hardcoded in civ) + starting_amount = dataset.genie_civs[1]["resources"][36].value + + elif line.get_id() == 199: + # Aqua harvester food amount (hardcoded in civ) + starting_amount = storage["amount"].value + starting_amount += dataset.genie_civs[1]["resources"][88].value + + else: + starting_amount = storage["amount"].value + + spot_raw_api_object.add_raw_member("starting_amount", + starting_amount, + "engine.util.resource_spot.ResourceSpot") + + # Max amount + spot_raw_api_object.add_raw_member("max_amount", + starting_amount, + "engine.util.resource_spot.ResourceSpot") + + # Decay rate + decay_rate = current_unit["resource_decay"].value + spot_raw_api_object.add_raw_member("decay_rate", + decay_rate, + "engine.util.resource_spot.ResourceSpot") + + spot_forward_ref = ForwardRef(line, spot_name) + ability_raw_api_object.add_raw_member("resources", + spot_forward_ref, + "engine.ability.type.Harvestable") + line.add_raw_api_object(spot_raw_api_object) + + # Only one resource spot per ability + break + + # Harvest Progress (we don't use this for SWGB) + ability_raw_api_object.add_raw_member("harvest_progress", + [], + "engine.ability.type.Harvestable") + + # Restock Progress + progress_forward_refs = [] + if line.get_class_id() == 7: + # Farms + # ===================================================================================== + progress_ref = f"{ability_ref}.RestockProgress33" + progress_raw_api_object = RawAPIObject(progress_ref, + "RestockProgress33", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member( + "type", + api_objects["engine.util.progress_type.type.Restock"], + "engine.util.progress.Progress" + ) + + # Interval = (0.0, 33.0) + progress_raw_api_object.add_raw_member("left_boundary", + 0.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 33.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction1" + terrain_group = dataset.terrain_groups[29] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + init_state_ref = f"{game_entity_name}.Constructable.InitState" + init_state_forward_ref = ForwardRef(line, init_state_ref) + property_raw_api_object.add_raw_member("state_change", + init_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.RestockProgress66" + progress_raw_api_object = RawAPIObject(progress_ref, + "RestockProgress66", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member( + "type", + api_objects["engine.util.progress_type.type.Restock"], + "engine.util.progress.Progress" + ) + + # Interval = (33.0, 66.0) + progress_raw_api_object.add_raw_member("left_boundary", + 33.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 66.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction2" + terrain_group = dataset.terrain_groups[30] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + construct_state_ref = f"{game_entity_name}.Constructable.ConstructState" + construct_state_forward_ref = ForwardRef(line, construct_state_ref) + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ===================================================================================== + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + # ===================================================================================== + progress_ref = f"{ability_ref}.RestockProgress100" + progress_raw_api_object = RawAPIObject(progress_ref, + "RestockProgress100", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, ability_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member( + "type", + api_objects["engine.util.progress_type.type.Restock"], + "engine.util.progress.Progress" + ) + + progress_raw_api_object.add_raw_member("left_boundary", + 66.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 100.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ===================================================================================== + # Terrain overlay property + # ===================================================================================== + property_ref = f"{progress_ref}.TerrainOverlay" + property_raw_api_object = RawAPIObject(property_ref, + "TerrainOverlay", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.TerrainOverlay") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # Terrain overlay + terrain_ref = "FarmConstruction3" + terrain_group = dataset.terrain_groups[31] + terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) + property_raw_api_object.add_raw_member("terrain_overlay", + terrain_forward_ref, + "engine.util.progress.property.type.TerrainOverlay") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref + }) + # ===================================================================================== + # State change property + # ===================================================================================== + property_ref = f"{progress_ref}.StateChange" + property_raw_api_object = RawAPIObject(property_ref, + "StateChange", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + + # State change + construct_state_ref = f"{game_entity_name}.Constructable.ConstructState" + construct_state_forward_ref = ForwardRef(line, construct_state_ref) + property_raw_api_object.add_raw_member("state_change", + construct_state_forward_ref, + "engine.util.progress.property.type.StateChange") + + property_forward_ref = ForwardRef(line, property_ref) + properties.update({ + api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref + }) + # ======================================================================= + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + + progress_forward_refs.append(ForwardRef(line, progress_ref)) + + ability_raw_api_object.add_raw_member("restock_progress", + progress_forward_refs, + "engine.ability.type.Harvestable") + + # Gatherer limit (infinite in SWGB except for farms) + gatherer_limit = MemberSpecialValue.NYAN_INF + if line.get_class_id() == 7: + gatherer_limit = 1 + + ability_raw_api_object.add_raw_member("gatherer_limit", + gatherer_limit, + "engine.ability.type.Harvestable") + + # Unit have to die before they are harvestable (except for farms) + harvestable_by_default = current_unit["hit_points"].value == 0 + if line.get_class_id() == 7: + harvestable_by_default = True + + ability_raw_api_object.add_raw_member("harvestable_by_default", + harvestable_by_default, + "engine.ability.type.Harvestable") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/idle.py b/openage/convert/processor/conversion/swgbcc/ability/idle.py new file mode 100644 index 0000000000..1280162526 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/idle.py @@ -0,0 +1,29 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Idle ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def idle_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Idle 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 + """ + ability_forward_ref = AoCAbilitySubprocessor.idle_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/line_of_sight.py b/openage/convert/processor/conversion/swgbcc/ability/line_of_sight.py new file mode 100644 index 0000000000..d492f7f446 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/line_of_sight.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the LineOfSight ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def line_of_sight_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the LineOfSight ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.los_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/live.py b/openage/convert/processor/conversion/swgbcc/ability/live.py new file mode 100644 index 0000000000..01bcf9275f --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/live.py @@ -0,0 +1,29 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the LineOfSight ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def live_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Live 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 + """ + ability_forward_ref = AoCAbilitySubprocessor.live_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/move.py b/openage/convert/processor/conversion/swgbcc/ability/move.py new file mode 100644 index 0000000000..72ead73b98 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/move.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Move ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def move_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Move ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.move_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/named.py b/openage/convert/processor/conversion/swgbcc/ability/named.py new file mode 100644 index 0000000000..f577edf0d2 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/named.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Named ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def named_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Named ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.named_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/provide_contingent.py b/openage/convert/processor/conversion/swgbcc/ability/provide_contingent.py new file mode 100644 index 0000000000..1937f53bfa --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/provide_contingent.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ProvideContingent ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def provide_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ProvideContingent ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.provide_contingent_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/regenerate_attribute.py b/openage/convert/processor/conversion/swgbcc/ability/regenerate_attribute.py new file mode 100644 index 0000000000..022dae93df --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/regenerate_attribute.py @@ -0,0 +1,93 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the RegenerateAttribute ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def regenerate_attribute_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the RegenerateAttribute ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward references for the ability. + """ + current_unit_id = line.get_head_unit_id() + dataset = line.data + + attribute = None + attribute_name = "" + if current_unit_id in (115, 180): + # Monk; regenerates Faith + attribute = dataset.pregen_nyan_objects["util.attribute.types.Faith"].get_nyan_object() + attribute_name = "Faith" + + elif current_unit_id == 8: + # Berserk: regenerates Health + attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() + attribute_name = "Health" + + else: + return [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_name = f"Regenerate{attribute_name}" + 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("engine.ability.type.RegenerateAttribute") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Attribute rate + # =============================================================================== + rate_name = f"{attribute_name}Rate" + rate_ref = f"{game_entity_name}.{ability_name}.{rate_name}" + rate_raw_api_object = RawAPIObject(rate_ref, rate_name, dataset.nyan_api_objects) + rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") + rate_location = ForwardRef(line, ability_ref) + rate_raw_api_object.set_location(rate_location) + + # Attribute + rate_raw_api_object.add_raw_member("type", + attribute, + "engine.util.attribute.AttributeRate") + + # Rate + attribute_rate = 0 + if current_unit_id in (115, 180): + # stored in civ resources + attribute_rate = dataset.genie_civs[0]["resources"][35].value + + elif current_unit_id == 8: + # stored in civ resources + heal_timer = dataset.genie_civs[0]["resources"][96].value + attribute_rate = heal_timer + + rate_raw_api_object.add_raw_member("rate", + attribute_rate, + "engine.util.attribute.AttributeRate") + + line.add_raw_api_object(rate_raw_api_object) + # =============================================================================== + rate_forward_ref = ForwardRef(line, rate_ref) + ability_raw_api_object.add_raw_member("rate", + rate_forward_ref, + "engine.ability.type.RegenerateAttribute") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return [ability_forward_ref] diff --git a/openage/convert/processor/conversion/swgbcc/ability/resource_storage.py b/openage/convert/processor/conversion/swgbcc/ability/resource_storage.py new file mode 100644 index 0000000000..544d7cc0f9 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/resource_storage.py @@ -0,0 +1,265 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ResourceStorage ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the ResourceStorage ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + if isinstance(line, GenieVillagerGroup): + gatherers = line.variants[0].line + + else: + gatherers = [line.line[0]] + + current_unit_id = line.get_head_unit_id() + dataset = line.data + api_objects = dataset.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.ResourceStorage" + ability_raw_api_object = RawAPIObject(ability_ref, "ResourceStorage", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.ResourceStorage") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Create containers + containers = [] + for gatherer in gatherers: + unit_commands = gatherer["unit_commands"].value + resource = None + + used_command = None + for command in unit_commands: + # Find a gather ability. It doesn't matter which one because + # they should all produce the same resource for one genie unit. + type_id = command["type"].value + + if type_id not in (5, 110, 111): + continue + + resource_id = command["resource_out"].value + + # If resource_out is not specified, the gatherer harvests resource_in + if resource_id == -1: + resource_id = command["resource_in"].value + + if resource_id == 0: + resource = dataset.pregen_nyan_objects[ + "util.resource.types.Food" + ].get_nyan_object() + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects[ + "util.resource.types.Carbon" + ].get_nyan_object() + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects[ + "util.resource.types.Ore" + ].get_nyan_object() + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects[ + "util.resource.types.Nova" + ].get_nyan_object() + + elif type_id == 111: + target_id = command["unit_id"].value + if target_id not in dataset.building_lines.keys(): + # Skips the trade workshop trading which is never used + continue + + # Trade goods --> nova + resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object( + ) + + else: + continue + + used_command = command + + if not used_command: + # 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: + # Skips hunting wolves + continue + + container_name = f"{gather_lookup_dict[gatherer_unit_id][0]}Container" + + elif used_command["type"].value == 111: + # Trading + container_name = "TradeContainer" + + container_ref = f"{ability_ref}.{container_name}" + container_raw_api_object = RawAPIObject( + container_ref, container_name, dataset.nyan_api_objects) + container_raw_api_object.add_raw_parent("engine.util.storage.ResourceContainer") + container_location = ForwardRef(line, ability_ref) + container_raw_api_object.set_location(container_location) + + line.add_raw_api_object(container_raw_api_object) + + # Resource + container_raw_api_object.add_raw_member("resource", + resource, + "engine.util.storage.ResourceContainer") + + # Carry capacity + carry_capacity = gatherer["resource_capacity"].value + container_raw_api_object.add_raw_member("max_amount", + carry_capacity, + "engine.util.storage.ResourceContainer") + + # Carry progress + carry_progress = [] + carry_move_animation_id = used_command["carry_sprite_id"].value + if carry_move_animation_id > -1: + # =========================================================================================== + progress_ref = f"{ability_ref}.{container_name}CarryProgress" + progress_raw_api_object = RawAPIObject(progress_ref, + f"{container_name}CarryProgress", + dataset.nyan_api_objects) + progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") + progress_location = ForwardRef(line, container_ref) + progress_raw_api_object.set_location(progress_location) + + line.add_raw_api_object(progress_raw_api_object) + + # Type + progress_raw_api_object.add_raw_member( + "type", + api_objects["engine.util.progress_type.type.Carry"], + "engine.util.progress.Progress" + ) + + # Interval = (20.0, 100.0) + progress_raw_api_object.add_raw_member("left_boundary", + 20.0, + "engine.util.progress.Progress") + progress_raw_api_object.add_raw_member("right_boundary", + 100.0, + "engine.util.progress.Progress") + + # Progress properties + properties = {} + # ================================================================================= + # Animated property (animation overrides) + # ================================================================================= + property_ref = f"{progress_ref}.Animated" + property_raw_api_object = RawAPIObject(property_ref, + "Animated", + dataset.nyan_api_objects) + property_raw_api_object.add_raw_parent( + "engine.util.progress.property.type.Animated") + property_location = ForwardRef(line, progress_ref) + property_raw_api_object.set_location(property_location) + + line.add_raw_api_object(property_raw_api_object) + # ================================================================================= + overrides = [] + # ================================================================================= + # Move override + # ================================================================================= + override_ref = f"{property_ref}.MoveOverride" + override_raw_api_object = RawAPIObject(override_ref, + "MoveOverride", + dataset.nyan_api_objects) + override_raw_api_object.add_raw_parent( + "engine.util.animation_override.AnimationOverride") + override_location = ForwardRef(line, property_ref) + override_raw_api_object.set_location(override_location) + + line.add_raw_api_object(override_raw_api_object) + + move_forward_ref = ForwardRef(line, f"{game_entity_name}.Move") + override_raw_api_object.add_raw_member( + "ability", + move_forward_ref, + "engine.util.animation_override.AnimationOverride" + ) + + # Animation + animations_set = [] + animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, + carry_move_animation_id, + override_ref, + "Move", + "move_carry_override_") + + animations_set.append(animation_forward_ref) + override_raw_api_object.add_raw_member( + "animations", + animations_set, + "engine.util.animation_override.AnimationOverride" + ) + + override_raw_api_object.add_raw_member( + "priority", + 1, + "engine.util.animation_override.AnimationOverride" + ) + + override_forward_ref = ForwardRef(line, override_ref) + overrides.append(override_forward_ref) + # ================================================================================= + property_raw_api_object.add_raw_member("overrides", + overrides, + "engine.util.progress.property.type.Animated") + + property_forward_ref = ForwardRef(line, property_ref) + + properties.update({ + api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref + }) + # ================================================================================= + progress_raw_api_object.add_raw_member("properties", + properties, + "engine.util.progress.Progress") + # ================================================================================= + progress_forward_ref = ForwardRef(line, progress_ref) + carry_progress.append(progress_forward_ref) + + container_raw_api_object.add_raw_member("carry_progress", + carry_progress, + "engine.util.storage.ResourceContainer") + + container_forward_ref = ForwardRef(line, container_ref) + containers.append(container_forward_ref) + + ability_raw_api_object.add_raw_member("containers", + containers, + "engine.ability.type.ResourceStorage") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/restock.py b/openage/convert/processor/conversion/swgbcc/ability/restock.py new file mode 100644 index 0000000000..46d38bf221 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/restock.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Restock ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def restock_ability(line: GenieGameEntityGroup, restock_target_id: int) -> ForwardRef: + """ + Adds the Restock ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.restock_ability(line, restock_target_id) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/selectable.py b/openage/convert/processor/conversion/swgbcc/ability/selectable.py new file mode 100644 index 0000000000..94e747759b --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/selectable.py @@ -0,0 +1,29 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Selectable ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def selectable_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds Selectable abilities to a line. Units will get two of these, + one Rectangle box for the Self stance and one MatchToSprite box + for other stances. + + :param line: Unit/Building line that gets the abilities. + :returns: The forward reference for the abilities. + """ + ability_forward_ref = AoCAbilitySubprocessor.selectable_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/send_back_to_task.py b/openage/convert/processor/conversion/swgbcc/ability/send_back_to_task.py new file mode 100644 index 0000000000..2f709bf13b --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/send_back_to_task.py @@ -0,0 +1,55 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the SendBackToTask ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def send_back_to_task_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the SendBackToTask ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + 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}.SendBackToTask" + ability_raw_api_object = RawAPIObject(ability_ref, + "SendBackToTask", + dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.SendBackToTask") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Only works on villagers + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Worker"].get_nyan_object() + ] + ability_raw_api_object.add_raw_member("allowed_types", + allowed_types, + "engine.ability.type.SendBackToTask") + ability_raw_api_object.add_raw_member( + "blacklisted_entities", + [], + "engine.ability.type.SendBackToTask" + ) + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/shoot_projectile.py b/openage/convert/processor/conversion/swgbcc/ability/shoot_projectile.py new file mode 100644 index 0000000000..b84ab683a2 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/shoot_projectile.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the ShootProjectile ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> ForwardRef: + """ + Adds the ShootProjectile ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.shoot_projectile_ability(line, command_id) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/trade.py b/openage/convert/processor/conversion/swgbcc/ability/trade.py new file mode 100644 index 0000000000..2df6789e4c --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/trade.py @@ -0,0 +1,76 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Trade ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def trade_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Trade ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + current_unit = line.get_head_unit() + 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}.Trade" + ability_raw_api_object = RawAPIObject(ability_ref, "Trade", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Trade") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Trade route (use the trade route o the market) + trade_routes = [] + + unit_commands = current_unit["unit_commands"].value + for command in unit_commands: + # Find the trade command and the trade post id + type_id = command["type"].value + + if type_id != 111: + continue + + trade_post_id = command["unit_id"].value + if trade_post_id == 530: + # Ignore Tattoine Spaceport + continue + + trade_post_line = dataset.building_lines[trade_post_id] + trade_post_name = name_lookup_dict[trade_post_id][0] + + trade_route_ref = f"{trade_post_name}.TradePost.AoE2{trade_post_name}TradeRoute" + trade_route_forward_ref = ForwardRef(trade_post_line, trade_route_ref) + trade_routes.append(trade_route_forward_ref) + + ability_raw_api_object.add_raw_member("trade_routes", + trade_routes, + "engine.ability.type.Trade") + + # container + container_forward_ref = ForwardRef( + line, f"{game_entity_name}.ResourceStorage.TradeContainer") + ability_raw_api_object.add_raw_member("container", + container_forward_ref, + "engine.ability.type.Trade") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/trade_post.py b/openage/convert/processor/conversion/swgbcc/ability/trade_post.py new file mode 100644 index 0000000000..47b85108a5 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/trade_post.py @@ -0,0 +1,77 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the TradePost ability. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def trade_post_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the TradePost ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + 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}.TradePost" + ability_raw_api_object = RawAPIObject(ability_ref, "TradePost", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.TradePost") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Trade route + trade_routes = [] + # ===================================================================================== + trade_route_name = f"AoE2{game_entity_name}TradeRoute" + trade_route_ref = f"{game_entity_name}.TradePost.{trade_route_name}" + trade_route_raw_api_object = RawAPIObject(trade_route_ref, + trade_route_name, + dataset.nyan_api_objects) + trade_route_raw_api_object.add_raw_parent("engine.util.trade_route.type.AoE2TradeRoute") + trade_route_location = ForwardRef(line, ability_ref) + trade_route_raw_api_object.set_location(trade_route_location) + + # Trade resource + resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() + trade_route_raw_api_object.add_raw_member("trade_resource", + resource, + "engine.util.trade_route.TradeRoute") + + # Start- and endpoints + market_forward_ref = ForwardRef(line, game_entity_name) + trade_route_raw_api_object.add_raw_member("start_trade_post", + market_forward_ref, + "engine.util.trade_route.TradeRoute") + trade_route_raw_api_object.add_raw_member("end_trade_post", + market_forward_ref, + "engine.util.trade_route.TradeRoute") + + trade_route_forward_ref = ForwardRef(line, trade_route_ref) + trade_routes.append(trade_route_forward_ref) + + line.add_raw_api_object(trade_route_raw_api_object) + # ===================================================================================== + ability_raw_api_object.add_raw_member("trade_routes", + trade_routes, + "engine.ability.type.TradePost") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability/turn.py b/openage/convert/processor/conversion/swgbcc/ability/turn.py new file mode 100644 index 0000000000..ec18a8f4d6 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/ability/turn.py @@ -0,0 +1,27 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for the Turn ability. +""" +from __future__ import annotations +import typing + +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def turn_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Turn ability to a line. + + :param line: Unit/Building line that gets the ability. + :returns: The forward reference for the ability. + """ + ability_forward_ref = AoCAbilitySubprocessor.turn_ability(line) + + # TODO: Implement diffing of civ lines + + return ability_forward_ref diff --git a/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py b/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py index 21c859027b..2f8e4a39f4 100644 --- a/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py @@ -1,11 +1,4 @@ # 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 -# pylint: disable=invalid-name -# -# TODO: -# pylint: disable=unused-argument,line-too-long """ Derives and adds abilities to lines. Subroutine of the @@ -14,22 +7,31 @@ For SWGB we use the functions of the AoCAbilitySubprocessor, but additionally create a diff for every civ line. """ -from __future__ import annotations -import typing - -from .....nyan.nyan_structs import MemberSpecialValue -from .....util.ordered_set import OrderedSet -from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ - GenieStackBuildingGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ..aoc.ability_subprocessor import AoCAbilitySubprocessor -from ..aoc.effect_subprocessor import AoCEffectSubprocessor - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup - from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup +from .ability.active_transform_to import active_transform_to_ability +from .ability.apply_continuous_effect import apply_continuous_effect_ability +from .ability.apply_discrete_effect import apply_discrete_effect_ability +from .ability.attribute_change_tracker import attribute_change_tracker_ability +from .ability.collision import collision_ability +from .ability.constructable import constructable_ability +from .ability.death import death_ability +from .ability.exchange_resources import exchange_resources_ability +from .ability.gather import gather_ability +from .ability.harvestable import harvestable_ability +from .ability.idle import idle_ability +from .ability.line_of_sight import line_of_sight_ability +from .ability.live import live_ability +from .ability.move import move_ability +from .ability.named import named_ability +from .ability.provide_contingent import provide_contingent_ability +from .ability.regenerate_attribute import regenerate_attribute_ability +from .ability.resource_storage import resource_storage_ability +from .ability.restock import restock_ability +from .ability.selectable import selectable_ability +from .ability.send_back_to_task import send_back_to_task_ability +from .ability.shoot_projectile import shoot_projectile_ability +from .ability.trade import trade_ability +from .ability.trade_post import trade_post_ability +from .ability.turn import turn_ability class SWGBCCAbilitySubprocessor: @@ -37,1868 +39,28 @@ class SWGBCCAbilitySubprocessor: Creates raw API objects for abilities in SWGB. """ - @staticmethod - def active_transform_to_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ActiveTransformTo 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.active_transform_to_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def apply_continuous_effect_ability( - line: GenieGameEntityGroup, - command_id: int, - ranged: bool = False - ) -> ForwardRef: - """ - Adds the ApplyContinuousEffect ability to a line that is used to make entities die. - - :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 - """ - ability_forward_ref = AoCAbilitySubprocessor.apply_continuous_effect_ability( - line, command_id, ranged) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def apply_discrete_effect_ability( - line: GenieGameEntityGroup, - command_id: int, - ranged: bool = False, - projectile: int = -1 - ) -> ForwardRef: - """ - Adds the ApplyDiscreteEffect ability to a line that is used to make entities die. - - :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 - """ - if isinstance(line, GenieVillagerGroup): - current_unit = line.get_units_with_command(command_id)[0] - current_unit_id = current_unit["id0"].value - - else: - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - - head_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - command_lookup_dict = internal_name_lookups.get_command_lookups(dataset.game_version) - gset_lookup_dict = internal_name_lookups.get_graphic_set_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - ability_name = command_lookup_dict[command_id][0] - 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_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - ability_animation_id = current_unit["attack_sprite_id"].value - - else: - ability_ref = f"{game_entity_name}.ShootProjectile.Projectile{str(projectile)}.{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_location = ForwardRef(line, - (f"{game_entity_name}.ShootProjectile." - f"Projectile{projectile}")) - ability_raw_api_object.set_location(ability_location) - - ability_animation_id = -1 - - # Ability properties - properties = {} - - # Animated - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - ability_animation_id, - property_ref, - ability_name, - f"{command_lookup_dict[command_id][1]}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Create custom civ graphics - handled_graphics_set_ids = set() - for civ_group in dataset.civ_groups.values(): - civ = civ_group.civ - civ_id = civ_group.get_id() - - # Only proceed if the civ stores the unit in the line - if current_unit_id not in civ["units"].value.keys(): - continue - - civ_animation_id = civ["units"][current_unit_id]["attack_sprite_id"].value - - if civ_animation_id != ability_animation_id: - # Find the corresponding graphics set - graphics_set_id = -1 - for set_id, items in gset_lookup_dict.items(): - if civ_id in items[0]: - graphics_set_id = set_id - break - - # Check if the object for the animation has been created before - obj_exists = graphics_set_id in handled_graphics_set_ids - if not obj_exists: - handled_graphics_set_ids.add(graphics_set_id) - - obj_prefix = f"{gset_lookup_dict[graphics_set_id][1]}{ability_name}" - filename_prefix = (f"{command_lookup_dict[command_id][1]}_" - f"{gset_lookup_dict[graphics_set_id][2]}_") - AoCAbilitySubprocessor.create_civ_animation(line, - civ_group, - civ_animation_id, - property_ref, - obj_prefix, - filename_prefix, - obj_exists) - - # Command Sound - if projectile == -1: - ability_comm_sound_id = current_unit["command_sound_id"].value - - else: - ability_comm_sound_id = -1 - - if ability_comm_sound_id > -1: - property_ref = f"{ability_ref}.CommandSound" - property_raw_api_object = RawAPIObject(property_ref, - "CommandSound", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.CommandSound") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - sounds_set = [] - - if projectile == -1: - sound_obj_prefix = ability_name - - else: - sound_obj_prefix = "ProjectileAttack" - - sound_forward_ref = AoCAbilitySubprocessor.create_sound(line, - ability_comm_sound_id, - property_ref, - sound_obj_prefix, - "command_") - sounds_set.append(sound_forward_ref) - property_raw_api_object.add_raw_member("sounds", sounds_set, - "engine.ability.property.type.CommandSound") - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.CommandSound"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - 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 - 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 - 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" - batch_raw_api_object = RawAPIObject(batch_ref, "Batch", dataset.nyan_api_objects) - batch_raw_api_object.add_raw_parent("engine.util.effect_batch.type.UnorderedBatch") - batch_location = ForwardRef(line, ability_ref) - batch_raw_api_object.set_location(batch_location) - - line.add_raw_api_object(batch_raw_api_object) - - # Effects - effects = [] - if command_id == 7: - # Attack - if projectile != 1: - effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref) - - else: - effects = AoCEffectSubprocessor.get_attack_effects(line, batch_ref, projectile=1) - - elif command_id == 104: - # Convert - effects = AoCEffectSubprocessor.get_convert_effects(line, batch_ref) - - batch_raw_api_object.add_raw_member("effects", - effects, - "engine.util.effect_batch.EffectBatch") - - batch_forward_ref = ForwardRef(line, batch_ref) - ability_raw_api_object.add_raw_member("batches", - [batch_forward_ref], - "engine.ability.type.ApplyDiscreteEffect") - - # Reload time - if projectile == -1: - reload_time = current_unit["attack_speed"].value - - else: - reload_time = 0 - - ability_raw_api_object.add_raw_member("reload_time", - reload_time, - "engine.ability.type.ApplyDiscreteEffect") - - # Application delay - if projectile == -1: - attack_graphic_id = current_unit["attack_sprite_id"].value - attack_graphic = dataset.genie_graphics[attack_graphic_id] - frame_rate = attack_graphic.get_frame_rate() - frame_delay = current_unit["frame_delay"].value - application_delay = frame_rate * frame_delay - - else: - application_delay = 0 - - ability_raw_api_object.add_raw_member("application_delay", - application_delay, - "engine.ability.type.ApplyDiscreteEffect") - - # Allowed types (all buildings/units) - if command_id == 104: - # Convert - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object()] - - else: - allowed_types = [dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object()] - - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.ApplyDiscreteEffect") - - if command_id == 104: - # Convert - force_master_line = dataset.unit_lines[115] - force_line = dataset.unit_lines[180] - artillery_line = dataset.unit_lines[691] - anti_air_line = dataset.unit_lines[702] - pummel_line = dataset.unit_lines[713] - - blacklisted_entities = [ForwardRef(force_master_line, "ForceMaster"), - ForwardRef(force_line, "ForceKnight"), - ForwardRef(artillery_line, "Artillery"), - ForwardRef(anti_air_line, "AntiAirMobile"), - ForwardRef(pummel_line, "Pummel")] - - else: - blacklisted_entities = [] - - ability_raw_api_object.add_raw_member("blacklisted_entities", - blacklisted_entities, - "engine.ability.type.ApplyDiscreteEffect") - - line.add_raw_api_object(ability_raw_api_object) - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def attribute_change_tracker_ability(line) -> ForwardRef: - """ - Adds the AttributeChangeTracker 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 - """ - if isinstance(line, GenieStackBuildingGroup): - current_unit = line.get_stack_unit() - current_unit_id = line.get_stack_unit_id() - - else: - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - - dataset = line.data - api_objects = dataset.nyan_api_objects - - 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}.AttributeChangeTracker" - ability_raw_api_object = RawAPIObject( - ability_ref, "AttributeChangeTracker", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.AttributeChangeTracker") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Attribute - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - ability_raw_api_object.add_raw_member("attribute", - attribute, - "engine.ability.type.AttributeChangeTracker") - - # Change progress - damage_graphics = current_unit["damage_graphics"].value - progress_forward_refs = [] - - # Damage graphics are ordered ascending, so we start from 0 - interval_left_bound = 0 - for damage_graphic_member in damage_graphics: - interval_right_bound = damage_graphic_member["damage_percent"].value - progress_ref = f"{game_entity_name}.AttributeChangeTracker.ChangeProgress{interval_right_bound}" - progress_raw_api_object = RawAPIObject(progress_ref, - f"ChangeProgress{interval_right_bound}", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.AttributeChange"], - "engine.util.progress.Progress") - - # Interval - progress_raw_api_object.add_raw_member("left_boundary", - interval_left_bound, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - interval_right_bound, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # AnimationOverlay property - # ===================================================================================== - progress_animation_id = damage_graphic_member["graphic_id"].value - if progress_animation_id > -1: - property_ref = f"{progress_ref}.AnimationOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "AnimationOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.AnimationOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - progress_animation_id, - property_ref, - "Idle", - f"idle_damage_override_{interval_right_bound}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("overlays", - animations_set, - "engine.util.progress.property.type.AnimationOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.AnimationOverlay"]: property_forward_ref - }) - - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - interval_left_bound = interval_right_bound - - ability_raw_api_object.add_raw_member("change_progress", - progress_forward_refs, - "engine.ability.type.AttributeChangeTracker") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - - @staticmethod - def constructable_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Constructable 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.constructable_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def death_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Death ability to a line that is used to make entities die. - - :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 - """ - ability_forward_ref = AoCAbilitySubprocessor.death_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def exchange_resources_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ExchangeResources 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] - - resource_names = ["Food", "Carbon", "Ore"] - - abilities = [] - for resource_name in resource_names: - ability_name = f"MarketExchange{resource_name}" - 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("engine.ability.type.ExchangeResources") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Resource that is exchanged (resource A) - resource_a = dataset.pregen_nyan_objects[f"util.resource.types.{resource_name}"].get_nyan_object( - ) - ability_raw_api_object.add_raw_member("resource_a", - resource_a, - "engine.ability.type.ExchangeResources") - - # Resource that is exchanged for (resource B) - resource_b = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() - ability_raw_api_object.add_raw_member("resource_b", - resource_b, - "engine.ability.type.ExchangeResources") - - # Exchange rate - exchange_rate_ref = f"util.resource.market_trading.Market{resource_name}ExchangeRate" - exchange_rate = dataset.pregen_nyan_objects[exchange_rate_ref].get_nyan_object() - ability_raw_api_object.add_raw_member("exchange_rate", - exchange_rate, - "engine.ability.type.ExchangeResources") - - # Exchange modes - buy_exchange_ref = "util.resource.market_trading.MarketBuyExchangeMode" - sell_exchange_ref = "util.resource.market_trading.MarketSellExchangeMode" - exchange_modes = [ - dataset.pregen_nyan_objects[buy_exchange_ref].get_nyan_object(), - dataset.pregen_nyan_objects[sell_exchange_ref].get_nyan_object(), - ] - ability_raw_api_object.add_raw_member("exchange_modes", - exchange_modes, - "engine.ability.type.ExchangeResources") - - line.add_raw_api_object(ability_raw_api_object) - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - abilities.append(ability_forward_ref) - - return abilities - - @staticmethod - def gather_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Gather abilities to a line. Unlike the other methods, this - creates multiple abilities. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward references for the abilities. - :rtype: list - """ - if isinstance(line, GenieVillagerGroup): - gatherers = line.variants[0].line - - else: - gatherers = [line.line[0]] - - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - abilities = [] - for gatherer in gatherers: - unit_commands = gatherer["unit_commands"].value - resource = None - ability_animation_id = -1 - harvestable_class_ids = OrderedSet() - harvestable_unit_ids = OrderedSet() - - for command in unit_commands: - # Find a gather ability. It doesn't matter which one because - # they should all produce the same resource for one genie unit. - type_id = command["type"].value - - if type_id not in (5, 110): - continue - - target_class_id = command["class_id"].value - if target_class_id > -1: - harvestable_class_ids.add(target_class_id) - - target_unit_id = command["unit_id"].value - if target_unit_id > -1: - harvestable_unit_ids.add(target_unit_id) - - resource_id = command["resource_out"].value - - # If resource_out is not specified, the gatherer harvests resource_in - if resource_id == -1: - resource_id = command["resource_in"].value - - if resource_id == 0: - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object( - ) - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object( - ) - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object( - ) - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object( - ) - - else: - continue - - if type_id == 110: - ability_animation_id = command["work_sprite_id"].value - - else: - ability_animation_id = command["proceed_sprite_id"].value - - # Look for the harvestable groups that match the class IDs and unit IDs - check_groups = [] - check_groups.extend(dataset.unit_lines.values()) - check_groups.extend(dataset.building_lines.values()) - check_groups.extend(dataset.ambient_groups.values()) - - harvestable_groups = [] - for group in check_groups: - if not group.is_harvestable(): - continue - - if group.get_class_id() in harvestable_class_ids: - harvestable_groups.append(group) - continue - - for unit_id in harvestable_unit_ids: - if group.contains_entity(unit_id): - harvestable_groups.append(group) - - if len(harvestable_groups) == 0: - # If no matching groups are found, then we don't - # need to create an ability. - continue - - gatherer_unit_id = gatherer.get_id() - if gatherer_unit_id not in gather_lookup_dict: - # Skips hunting wolves - continue - - ability_name = gather_lookup_dict[gatherer_unit_id][0] - - 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("engine.ability.type.Gather") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - line.add_raw_api_object(ability_raw_api_object) - - # Ability properties - properties = {} - - # Animation - if ability_animation_id > -1: - property_ref = f"{ability_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Animated") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation( - line, - ability_animation_id, - property_ref, - ability_name, - f"{gather_lookup_dict[gatherer_unit_id][1]}_" - ) - animations_set.append(animation_forward_ref) - property_raw_api_object.add_raw_member("animations", animations_set, - "engine.ability.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Animated"]: property_forward_ref - }) - - # Diplomacy settings - property_ref = f"{ability_ref}.Diplomatic" - property_raw_api_object = RawAPIObject(property_ref, - "Diplomatic", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.ability.property.type.Diplomatic") - property_location = ForwardRef(line, ability_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - diplomatic_stances = [ - dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"]] - property_raw_api_object.add_raw_member("stances", diplomatic_stances, - "engine.ability.property.type.Diplomatic") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.ability.property.type.Diplomatic"]: property_forward_ref - }) - - ability_raw_api_object.add_raw_member("properties", - properties, - "engine.ability.Ability") - - # Auto resume - ability_raw_api_object.add_raw_member("auto_resume", - True, - "engine.ability.type.Gather") - - # search range - ability_raw_api_object.add_raw_member("resume_search_range", - MemberSpecialValue.NYAN_INF, - "engine.ability.type.Gather") - - # Gather rate - rate_name = f"{game_entity_name}.{ability_name}.GatherRate" - rate_raw_api_object = RawAPIObject(rate_name, "GatherRate", dataset.nyan_api_objects) - rate_raw_api_object.add_raw_parent("engine.util.resource.ResourceRate") - rate_location = ForwardRef(line, ability_ref) - rate_raw_api_object.set_location(rate_location) - - rate_raw_api_object.add_raw_member( - "type", resource, "engine.util.resource.ResourceRate") - - gather_rate = gatherer["work_rate"].value - rate_raw_api_object.add_raw_member( - "rate", gather_rate, "engine.util.resource.ResourceRate") - - line.add_raw_api_object(rate_raw_api_object) - - rate_forward_ref = ForwardRef(line, rate_name) - ability_raw_api_object.add_raw_member("gather_rate", - rate_forward_ref, - "engine.ability.type.Gather") - - # Resource container - container_ref = (f"{game_entity_name}.ResourceStorage." - f"{gather_lookup_dict[gatherer_unit_id][0]}Container") - container_forward_ref = ForwardRef(line, container_ref) - ability_raw_api_object.add_raw_member("container", - container_forward_ref, - "engine.ability.type.Gather") - - # Targets (resource spots) - entity_lookups = internal_name_lookups.get_entity_lookups(dataset.game_version) - spot_forward_refs = [] - for group in harvestable_groups: - group_id = group.get_head_unit_id() - group_name = entity_lookups[group_id][0] - - spot_forward_ref = ForwardRef( - group, - f"{group_name}.Harvestable.{group_name}ResourceSpot" - ) - spot_forward_refs.append(spot_forward_ref) - - ability_raw_api_object.add_raw_member("targets", - spot_forward_refs, - "engine.ability.type.Gather") - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - abilities.append(ability_forward_ref) - - return abilities - - @staticmethod - def harvestable_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Harvestable 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 = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - 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}.Harvestable" - ability_raw_api_object = RawAPIObject(ability_ref, "Harvestable", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Harvestable") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Resource spot - resource_storage = current_unit["resource_storage"].value - - for storage in resource_storage: - resource_id = storage["type"].value - - # IDs 15, 16, 17 are other types of food (meat, berries, fish) - if resource_id in (0, 15, 16, 17): - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object( - ) - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object() - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() - - else: - continue - - spot_name = f"{game_entity_name}.Harvestable.{game_entity_name}ResourceSpot" - spot_raw_api_object = RawAPIObject(spot_name, - f"{game_entity_name}ResourceSpot", - dataset.nyan_api_objects) - spot_raw_api_object.add_raw_parent("engine.util.resource_spot.ResourceSpot") - spot_location = ForwardRef(line, ability_ref) - spot_raw_api_object.set_location(spot_location) - - # Type - spot_raw_api_object.add_raw_member("resource", - resource, - "engine.util.resource_spot.ResourceSpot") - - # Start amount (equals max amount) - if line.get_id() == 50: - # Farm food amount (hardcoded in civ) - starting_amount = dataset.genie_civs[1]["resources"][36].value - - elif line.get_id() == 199: - # Aqua harvester food amount (hardcoded in civ) - starting_amount = storage["amount"].value - starting_amount += dataset.genie_civs[1]["resources"][88].value - - else: - starting_amount = storage["amount"].value - - spot_raw_api_object.add_raw_member("starting_amount", - starting_amount, - "engine.util.resource_spot.ResourceSpot") - - # Max amount - spot_raw_api_object.add_raw_member("max_amount", - starting_amount, - "engine.util.resource_spot.ResourceSpot") - - # Decay rate - decay_rate = current_unit["resource_decay"].value - spot_raw_api_object.add_raw_member("decay_rate", - decay_rate, - "engine.util.resource_spot.ResourceSpot") - - spot_forward_ref = ForwardRef(line, spot_name) - ability_raw_api_object.add_raw_member("resources", - spot_forward_ref, - "engine.ability.type.Harvestable") - line.add_raw_api_object(spot_raw_api_object) - - # Only one resource spot per ability - break - - # Harvest Progress (we don't use this for SWGB) - ability_raw_api_object.add_raw_member("harvest_progress", - [], - "engine.ability.type.Harvestable") - - # Restock Progress - progress_forward_refs = [] - if line.get_class_id() == 7: - # Farms - # ===================================================================================== - progress_ref = f"{ability_ref}.RestockProgress33" - progress_raw_api_object = RawAPIObject(progress_ref, - "RestockProgress33", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Restock"], - "engine.util.progress.Progress") - - # Interval = (0.0, 33.0) - progress_raw_api_object.add_raw_member("left_boundary", - 0.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 33.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction1" - terrain_group = dataset.terrain_groups[29] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - init_state_ref = f"{game_entity_name}.Constructable.InitState" - init_state_forward_ref = ForwardRef(line, init_state_ref) - property_raw_api_object.add_raw_member("state_change", - init_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.RestockProgress66" - progress_raw_api_object = RawAPIObject(progress_ref, - "RestockProgress66", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Restock"], - "engine.util.progress.Progress") - - # Interval = (33.0, 66.0) - progress_raw_api_object.add_raw_member("left_boundary", - 33.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 66.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction2" - terrain_group = dataset.terrain_groups[30] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - construct_state_ref = f"{game_entity_name}.Constructable.ConstructState" - construct_state_forward_ref = ForwardRef(line, construct_state_ref) - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ===================================================================================== - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - # ===================================================================================== - progress_ref = f"{ability_ref}.RestockProgress100" - progress_raw_api_object = RawAPIObject(progress_ref, - "RestockProgress100", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, ability_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Restock"], - "engine.util.progress.Progress") - - progress_raw_api_object.add_raw_member("left_boundary", - 66.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 100.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ===================================================================================== - # Terrain overlay property - # ===================================================================================== - property_ref = f"{progress_ref}.TerrainOverlay" - property_raw_api_object = RawAPIObject(property_ref, - "TerrainOverlay", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.TerrainOverlay") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # Terrain overlay - terrain_ref = "FarmConstruction3" - terrain_group = dataset.terrain_groups[31] - terrain_forward_ref = ForwardRef(terrain_group, terrain_ref) - property_raw_api_object.add_raw_member("terrain_overlay", - terrain_forward_ref, - "engine.util.progress.property.type.TerrainOverlay") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.TerrainOverlay"]: property_forward_ref - }) - # ===================================================================================== - # State change property - # ===================================================================================== - property_ref = f"{progress_ref}.StateChange" - property_raw_api_object = RawAPIObject(property_ref, - "StateChange", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent("engine.util.progress.property.type.StateChange") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - - # State change - construct_state_ref = f"{game_entity_name}.Constructable.ConstructState" - construct_state_forward_ref = ForwardRef(line, construct_state_ref) - property_raw_api_object.add_raw_member("state_change", - construct_state_forward_ref, - "engine.util.progress.property.type.StateChange") - - property_forward_ref = ForwardRef(line, property_ref) - properties.update({ - api_objects["engine.util.progress.property.type.StateChange"]: property_forward_ref - }) - # ======================================================================= - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - - progress_forward_refs.append(ForwardRef(line, progress_ref)) - - ability_raw_api_object.add_raw_member("restock_progress", - progress_forward_refs, - "engine.ability.type.Harvestable") - - # Gatherer limit (infinite in SWGB except for farms) - gatherer_limit = MemberSpecialValue.NYAN_INF - if line.get_class_id() == 7: - gatherer_limit = 1 - - ability_raw_api_object.add_raw_member("gatherer_limit", - gatherer_limit, - "engine.ability.type.Harvestable") - - # Unit have to die before they are harvestable (except for farms) - harvestable_by_default = current_unit["hit_points"].value == 0 - if line.get_class_id() == 7: - harvestable_by_default = True - - ability_raw_api_object.add_raw_member("harvestable_by_default", - harvestable_by_default, - "engine.ability.type.Harvestable") - - line.add_raw_api_object(ability_raw_api_object) - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def collision_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Collision 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.collision_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def idle_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Idle 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.idle_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def live_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Live 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.live_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def los_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the LineOfSight 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.los_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def move_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Move 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.move_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def named_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Named 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.named_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def provide_contingent_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ProvideContingent 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.provide_contingent_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def regenerate_attribute_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the RegenerateAttribute ability to a line. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward references for the ability. - :rtype: list - """ - current_unit_id = line.get_head_unit_id() - dataset = line.data - - attribute = None - attribute_name = "" - if current_unit_id in (115, 180): - # Monk; regenerates Faith - attribute = dataset.pregen_nyan_objects["util.attribute.types.Faith"].get_nyan_object() - attribute_name = "Faith" - - elif current_unit_id == 8: - # Berserk: regenerates Health - attribute = dataset.pregen_nyan_objects["util.attribute.types.Health"].get_nyan_object() - attribute_name = "Health" - - else: - return [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_name = f"Regenerate{attribute_name}" - 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("engine.ability.type.RegenerateAttribute") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Attribute rate - # =============================================================================== - rate_name = f"{attribute_name}Rate" - rate_ref = f"{game_entity_name}.{ability_name}.{rate_name}" - rate_raw_api_object = RawAPIObject(rate_ref, rate_name, dataset.nyan_api_objects) - rate_raw_api_object.add_raw_parent("engine.util.attribute.AttributeRate") - rate_location = ForwardRef(line, ability_ref) - rate_raw_api_object.set_location(rate_location) - - # Attribute - rate_raw_api_object.add_raw_member("type", - attribute, - "engine.util.attribute.AttributeRate") - - # Rate - attribute_rate = 0 - if current_unit_id in (115, 180): - # stored in civ resources - attribute_rate = dataset.genie_civs[0]["resources"][35].value - - elif current_unit_id == 8: - # stored in civ resources - heal_timer = dataset.genie_civs[0]["resources"][96].value - attribute_rate = heal_timer - - rate_raw_api_object.add_raw_member("rate", - attribute_rate, - "engine.util.attribute.AttributeRate") - - line.add_raw_api_object(rate_raw_api_object) - # =============================================================================== - rate_forward_ref = ForwardRef(line, rate_ref) - ability_raw_api_object.add_raw_member("rate", - rate_forward_ref, - "engine.ability.type.RegenerateAttribute") - - 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 resource_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the ResourceStorage 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 - """ - if isinstance(line, GenieVillagerGroup): - gatherers = line.variants[0].line - - else: - gatherers = [line.line[0]] - - current_unit_id = line.get_head_unit_id() - dataset = line.data - api_objects = dataset.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - gather_lookup_dict = internal_name_lookups.get_gather_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.ResourceStorage" - ability_raw_api_object = RawAPIObject(ability_ref, "ResourceStorage", - dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.ResourceStorage") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Create containers - containers = [] - for gatherer in gatherers: - unit_commands = gatherer["unit_commands"].value - resource = None - - used_command = None - for command in unit_commands: - # Find a gather ability. It doesn't matter which one because - # they should all produce the same resource for one genie unit. - type_id = command["type"].value - - if type_id not in (5, 110, 111): - continue - - resource_id = command["resource_out"].value - - # If resource_out is not specified, the gatherer harvests resource_in - if resource_id == -1: - resource_id = command["resource_in"].value - - if resource_id == 0: - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object( - ) - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object( - ) - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object( - ) - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object( - ) - - elif type_id == 111: - target_id = command["unit_id"].value - if target_id not in dataset.building_lines.keys(): - # Skips the trade workshop trading which is never used - continue - - # Trade goods --> nova - resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object( - ) - - else: - continue - - used_command = command - - if not used_command: - # 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: - # Skips hunting wolves - continue - - container_name = f"{gather_lookup_dict[gatherer_unit_id][0]}Container" - - elif used_command["type"].value == 111: - # Trading - container_name = "TradeContainer" - - container_ref = f"{ability_ref}.{container_name}" - container_raw_api_object = RawAPIObject( - container_ref, container_name, dataset.nyan_api_objects) - container_raw_api_object.add_raw_parent("engine.util.storage.ResourceContainer") - container_location = ForwardRef(line, ability_ref) - container_raw_api_object.set_location(container_location) - - line.add_raw_api_object(container_raw_api_object) - - # Resource - container_raw_api_object.add_raw_member("resource", - resource, - "engine.util.storage.ResourceContainer") - - # Carry capacity - carry_capacity = gatherer["resource_capacity"].value - container_raw_api_object.add_raw_member("max_amount", - carry_capacity, - "engine.util.storage.ResourceContainer") - - # Carry progress - carry_progress = [] - carry_move_animation_id = used_command["carry_sprite_id"].value - if carry_move_animation_id > -1: - # =========================================================================================== - progress_ref = f"{ability_ref}.{container_name}CarryProgress" - progress_raw_api_object = RawAPIObject(progress_ref, - f"{container_name}CarryProgress", - dataset.nyan_api_objects) - progress_raw_api_object.add_raw_parent("engine.util.progress.Progress") - progress_location = ForwardRef(line, container_ref) - progress_raw_api_object.set_location(progress_location) - - line.add_raw_api_object(progress_raw_api_object) - - # Type - progress_raw_api_object.add_raw_member("type", - api_objects["engine.util.progress_type.type.Carry"], - "engine.util.progress.Progress") - - # Interval = (20.0, 100.0) - progress_raw_api_object.add_raw_member("left_boundary", - 20.0, - "engine.util.progress.Progress") - progress_raw_api_object.add_raw_member("right_boundary", - 100.0, - "engine.util.progress.Progress") - - # Progress properties - properties = {} - # ================================================================================= - # Animated property (animation overrides) - # ================================================================================= - property_ref = f"{progress_ref}.Animated" - property_raw_api_object = RawAPIObject(property_ref, - "Animated", - dataset.nyan_api_objects) - property_raw_api_object.add_raw_parent( - "engine.util.progress.property.type.Animated") - property_location = ForwardRef(line, progress_ref) - property_raw_api_object.set_location(property_location) - - line.add_raw_api_object(property_raw_api_object) - # ================================================================================= - overrides = [] - # ================================================================================= - # Move override - # ================================================================================= - override_ref = f"{property_ref}.MoveOverride" - override_raw_api_object = RawAPIObject(override_ref, - "MoveOverride", - dataset.nyan_api_objects) - override_raw_api_object.add_raw_parent( - "engine.util.animation_override.AnimationOverride") - override_location = ForwardRef(line, property_ref) - override_raw_api_object.set_location(override_location) - - line.add_raw_api_object(override_raw_api_object) - - move_forward_ref = ForwardRef(line, f"{game_entity_name}.Move") - override_raw_api_object.add_raw_member("ability", - move_forward_ref, - "engine.util.animation_override.AnimationOverride") - - # Animation - animations_set = [] - animation_forward_ref = AoCAbilitySubprocessor.create_animation(line, - carry_move_animation_id, - override_ref, - "Move", - "move_carry_override_") - - animations_set.append(animation_forward_ref) - override_raw_api_object.add_raw_member("animations", - animations_set, - "engine.util.animation_override.AnimationOverride") - - override_raw_api_object.add_raw_member("priority", - 1, - "engine.util.animation_override.AnimationOverride") - - override_forward_ref = ForwardRef(line, override_ref) - overrides.append(override_forward_ref) - # ================================================================================= - property_raw_api_object.add_raw_member("overrides", - overrides, - "engine.util.progress.property.type.Animated") - - property_forward_ref = ForwardRef(line, property_ref) - - properties.update({ - api_objects["engine.util.progress.property.type.Animated"]: property_forward_ref - }) - # ================================================================================= - progress_raw_api_object.add_raw_member("properties", - properties, - "engine.util.progress.Progress") - # ================================================================================= - progress_forward_ref = ForwardRef(line, progress_ref) - carry_progress.append(progress_forward_ref) - - container_raw_api_object.add_raw_member("carry_progress", - carry_progress, - "engine.util.storage.ResourceContainer") - - container_forward_ref = ForwardRef(line, container_ref) - containers.append(container_forward_ref) - - ability_raw_api_object.add_raw_member("containers", - containers, - "engine.ability.type.ResourceStorage") - - 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 restock_ability(line: GenieGameEntityGroup, restock_target_id: int) -> ForwardRef: - """ - Adds the Restock 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.restock_ability(line, restock_target_id) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def selectable_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds Selectable abilities to a line. Units will get two of these, - one Rectangle box for the Self stance and one MatchToSprite box - for other stances. - - :param line: Unit/Building line that gets the abilities. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward reference for the abilities. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - ability_forward_ref = AoCAbilitySubprocessor.selectable_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def send_back_to_task_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the SendBackToTask 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}.SendBackToTask" - ability_raw_api_object = RawAPIObject( - ability_ref, "SendBackToTask", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.SendBackToTask") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Only works on villagers - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Worker"].get_nyan_object()] - ability_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.ability.type.SendBackToTask") - ability_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.ability.type.SendBackToTask") - - 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 shoot_projectile_ability(line: GenieGameEntityGroup, command_id: int) -> ForwardRef: - """ - Adds the ShootProjectile 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.shoot_projectile_ability(line, command_id) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref - - @staticmethod - def trade_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Trade 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 = line.get_head_unit() - 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}.Trade" - ability_raw_api_object = RawAPIObject(ability_ref, "Trade", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Trade") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Trade route (use the trade route o the market) - trade_routes = [] - - unit_commands = current_unit["unit_commands"].value - for command in unit_commands: - # Find the trade command and the trade post id - type_id = command["type"].value - - if type_id != 111: - continue - - trade_post_id = command["unit_id"].value - if trade_post_id == 530: - # Ignore Tattoine Spaceport - continue - - trade_post_line = dataset.building_lines[trade_post_id] - trade_post_name = name_lookup_dict[trade_post_id][0] - - trade_route_ref = f"{trade_post_name}.TradePost.AoE2{trade_post_name}TradeRoute" - trade_route_forward_ref = ForwardRef(trade_post_line, trade_route_ref) - trade_routes.append(trade_route_forward_ref) - - ability_raw_api_object.add_raw_member("trade_routes", - trade_routes, - "engine.ability.type.Trade") - - # container - container_forward_ref = ForwardRef( - line, f"{game_entity_name}.ResourceStorage.TradeContainer") - ability_raw_api_object.add_raw_member("container", - container_forward_ref, - "engine.ability.type.Trade") - - 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 trade_post_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the TradePost 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}.TradePost" - ability_raw_api_object = RawAPIObject(ability_ref, "TradePost", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.TradePost") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Trade route - trade_routes = [] - # ===================================================================================== - trade_route_name = f"AoE2{game_entity_name}TradeRoute" - trade_route_ref = f"{game_entity_name}.TradePost.{trade_route_name}" - trade_route_raw_api_object = RawAPIObject(trade_route_ref, - trade_route_name, - dataset.nyan_api_objects) - trade_route_raw_api_object.add_raw_parent("engine.util.trade_route.type.AoE2TradeRoute") - trade_route_location = ForwardRef(line, ability_ref) - trade_route_raw_api_object.set_location(trade_route_location) - - # Trade resource - resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() - trade_route_raw_api_object.add_raw_member("trade_resource", - resource, - "engine.util.trade_route.TradeRoute") - - # Start- and endpoints - market_forward_ref = ForwardRef(line, game_entity_name) - trade_route_raw_api_object.add_raw_member("start_trade_post", - market_forward_ref, - "engine.util.trade_route.TradeRoute") - trade_route_raw_api_object.add_raw_member("end_trade_post", - market_forward_ref, - "engine.util.trade_route.TradeRoute") - - trade_route_forward_ref = ForwardRef(line, trade_route_ref) - trade_routes.append(trade_route_forward_ref) - - line.add_raw_api_object(trade_route_raw_api_object) - # ===================================================================================== - ability_raw_api_object.add_raw_member("trade_routes", - trade_routes, - "engine.ability.type.TradePost") - - 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 turn_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Turn 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 - """ - ability_forward_ref = AoCAbilitySubprocessor.turn_ability(line) - - # TODO: Implement diffing of civ lines - - return ability_forward_ref + active_transform_to_ability = staticmethod(active_transform_to_ability) + apply_continuous_effect_ability = staticmethod(apply_continuous_effect_ability) + apply_discrete_effect_ability = staticmethod(apply_discrete_effect_ability) + attribute_change_tracker_ability = staticmethod(attribute_change_tracker_ability) + collision_ability = staticmethod(collision_ability) + constructable_ability = staticmethod(constructable_ability) + death_ability = staticmethod(death_ability) + exchange_resources_ability = staticmethod(exchange_resources_ability) + gather_ability = staticmethod(gather_ability) + harvestable_ability = staticmethod(harvestable_ability) + idle_ability = staticmethod(idle_ability) + live_ability = staticmethod(live_ability) + los_ability = staticmethod(line_of_sight_ability) + move_ability = staticmethod(move_ability) + named_ability = staticmethod(named_ability) + provide_contingent_ability = staticmethod(provide_contingent_ability) + regenerate_attribute_ability = staticmethod(regenerate_attribute_ability) + resource_storage_ability = staticmethod(resource_storage_ability) + restock_ability = staticmethod(restock_ability) + selectable_ability = staticmethod(selectable_ability) + send_back_to_task_ability = staticmethod(send_back_to_task_ability) + shoot_projectile_ability = staticmethod(shoot_projectile_ability) + trade_ability = staticmethod(trade_ability) + trade_post_ability = staticmethod(trade_post_ability) + turn_ability = staticmethod(turn_ability) From cd09a114e123570c1f296b61c48a3c21d68f04e1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 14:09:49 +0200 Subject: [PATCH 146/163] convert: Refactor SWBGCCAuxiliarySubprocessor into separate files. --- .../conversion/swgbcc/CMakeLists.txt | 1 + .../swgbcc/auxiliary/CMakeLists.txt | 5 + .../conversion/swgbcc/auxiliary/__init__.py | 6 + .../swgbcc/auxiliary/creatable_game_entity.py | 391 ++++++++++++ .../swgbcc/auxiliary/researchable_tech.py | 184 ++++++ .../swgbcc/auxiliary_subprocessor.py | 566 +----------------- 6 files changed, 591 insertions(+), 562 deletions(-) create mode 100644 openage/convert/processor/conversion/swgbcc/auxiliary/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/auxiliary/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/auxiliary/creatable_game_entity.py create mode 100644 openage/convert/processor/conversion/swgbcc/auxiliary/researchable_tech.py diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index ea016e251f..2a4ee4c7e8 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -13,3 +13,4 @@ add_py_modules( ) add_subdirectory(ability) +add_subdirectory(auxiliary) diff --git a/openage/convert/processor/conversion/swgbcc/auxiliary/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/auxiliary/CMakeLists.txt new file mode 100644 index 0000000000..476c335f94 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/auxiliary/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py + creatable_game_entity.py + researchable_tech.py +) diff --git a/openage/convert/processor/conversion/swgbcc/auxiliary/__init__.py b/openage/convert/processor/conversion/swgbcc/auxiliary/__init__.py new file mode 100644 index 0000000000..d2825e251d --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/auxiliary/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Derives creatables or researchables objects from unit lines, techs +or other objects. +""" diff --git a/openage/convert/processor/conversion/swgbcc/auxiliary/creatable_game_entity.py b/openage/convert/processor/conversion/swgbcc/auxiliary/creatable_game_entity.py new file mode 100644 index 0000000000..08afc4d6e0 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/auxiliary/creatable_game_entity.py @@ -0,0 +1,391 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for creatables (units). +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ + GenieBuildingLineGroup, GenieUnitLineGroup +from .....entity_object.conversion.combined_sound import CombinedSound +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def get_creatable_game_entity(line: GenieGameEntityGroup) -> None: + """ + Creates the CreatableGameEntity object for a unit/building line. + + :param line: Unit/Building line. + """ + if isinstance(line, GenieVillagerGroup): + current_unit = line.variants[0].line[0] + + else: + current_unit = line.line[0] + + current_unit_id = line.get_head_unit_id() + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + obj_ref = f"{game_entity_name}.CreatableGameEntity" + obj_name = f"{game_entity_name}Creatable" + creatable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) + creatable_raw_api_object.add_raw_parent("engine.util.create.CreatableGameEntity") + + # Get train location of line + train_location_id = line.get_train_location_id() + if isinstance(line, GenieBuildingLineGroup): + train_location = dataset.unit_lines[train_location_id] + train_location_name = name_lookup_dict[train_location_id][0] + + else: + train_location = dataset.building_lines[train_location_id] + train_location_name = name_lookup_dict[train_location_id][0] + + # Location of the object depends on whether it'a a unique unit or a normal unit + if line.is_unique(): + # Add object to the Civ object + enabling_research_id = line.get_enabling_research_id() + enabling_research = dataset.genie_techs[enabling_research_id] + enabling_civ_id = enabling_research["civilization_id"].value + + civ = dataset.civ_groups[enabling_civ_id] + civ_name = civ_lookup_dict[enabling_civ_id][0] + + creatable_location = ForwardRef(civ, civ_name) + + else: + # Add object to the train location's Create ability + creatable_location = ForwardRef(train_location, + f"{train_location_name}.Create") + + creatable_raw_api_object.set_location(creatable_location) + + # Game Entity + game_entity_forward_ref = ForwardRef(line, game_entity_name) + creatable_raw_api_object.add_raw_member("game_entity", + game_entity_forward_ref, + "engine.util.create.CreatableGameEntity") + + # TODO: Variants + variants_set = [] + + creatable_raw_api_object.add_raw_member("variants", variants_set, + "engine.util.create.CreatableGameEntity") + + # Cost (construction) + cost_name = f"{game_entity_name}.CreatableGameEntity.{game_entity_name}Cost" + cost_raw_api_object = RawAPIObject(cost_name, + f"{game_entity_name}Cost", + dataset.nyan_api_objects) + cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") + creatable_forward_ref = ForwardRef(line, obj_ref) + cost_raw_api_object.set_location(creatable_forward_ref) + + payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] + cost_raw_api_object.add_raw_member("payment_mode", + payment_mode, + "engine.util.cost.Cost") + + if line.is_repairable(): + # Cost (repair) for buildings + cost_repair_name = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}RepairCost") + cost_repair_raw_api_object = RawAPIObject(cost_repair_name, + f"{game_entity_name}RepairCost", + dataset.nyan_api_objects) + cost_repair_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") + creatable_forward_ref = ForwardRef(line, obj_ref) + cost_repair_raw_api_object.set_location(creatable_forward_ref) + + payment_repair_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Adaptive"] + cost_repair_raw_api_object.add_raw_member("payment_mode", + payment_repair_mode, + "engine.util.cost.Cost") + line.add_raw_api_object(cost_repair_raw_api_object) + + cost_amounts = [] + cost_repair_amounts = [] + for resource_amount in current_unit["resource_cost"].value: + resource_id = resource_amount["type_id"].value + + resource = None + resource_name = "" + if resource_id == -1: + # Not a valid resource + continue + + if resource_id == 0: + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + resource_name = "Food" + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object( + ) + resource_name = "Carbon" + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object() + resource_name = "Ore" + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() + resource_name = "Nova" + + else: + # Other resource ids are handled differently + continue + + # Skip resources that are only expected to be there + if not resource_amount["enabled"].value: + continue + + amount = resource_amount["amount"].value + + cost_amount_name = f"{cost_name}.{resource_name}Amount" + cost_amount = RawAPIObject(cost_amount_name, + f"{resource_name}Amount", + dataset.nyan_api_objects) + cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") + cost_forward_ref = ForwardRef(line, cost_name) + cost_amount.set_location(cost_forward_ref) + + cost_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + cost_amount.add_raw_member("amount", + amount, + "engine.util.resource.ResourceAmount") + + cost_amount_forward_ref = ForwardRef(line, cost_amount_name) + cost_amounts.append(cost_amount_forward_ref) + line.add_raw_api_object(cost_amount) + + if line.is_repairable(): + # Cost for repairing = half of the construction cost + cost_amount_name = f"{cost_repair_name}.{resource_name}Amount" + cost_amount = RawAPIObject(cost_amount_name, + f"{resource_name}Amount", + dataset.nyan_api_objects) + cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") + cost_forward_ref = ForwardRef(line, cost_repair_name) + cost_amount.set_location(cost_forward_ref) + + cost_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + cost_amount.add_raw_member("amount", + amount / 2, + "engine.util.resource.ResourceAmount") + + cost_amount_forward_ref = ForwardRef(line, cost_amount_name) + cost_repair_amounts.append(cost_amount_forward_ref) + line.add_raw_api_object(cost_amount) + + cost_raw_api_object.add_raw_member("amount", + cost_amounts, + "engine.util.cost.type.ResourceCost") + + if line.is_repairable(): + cost_repair_raw_api_object.add_raw_member("amount", + cost_repair_amounts, + "engine.util.cost.type.ResourceCost") + + cost_forward_ref = ForwardRef(line, cost_name) + creatable_raw_api_object.add_raw_member("cost", + cost_forward_ref, + "engine.util.create.CreatableGameEntity") + # Creation time + if isinstance(line, GenieUnitLineGroup): + creation_time = current_unit["creation_time"].value + + else: + # Buildings are created immediately + creation_time = 0 + + creatable_raw_api_object.add_raw_member("creation_time", + creation_time, + "engine.util.create.CreatableGameEntity") + + # Creation sound + creation_sound_id = current_unit["train_sound_id"].value + + # Create sound object + obj_name = f"{game_entity_name}.CreatableGameEntity.Sound" + sound_raw_api_object = RawAPIObject(obj_name, "CreationSound", + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(line, obj_ref) + sound_raw_api_object.set_location(sound_location) + + # Search for the sound if it exists + creation_sounds = [] + if creation_sound_id > -1: + genie_sound = dataset.genie_sounds[creation_sound_id] + file_ids = genie_sound.get_sounds(civ_id=-1) + + if file_ids: + file_id = genie_sound.get_sounds(civ_id=-1)[0] + + if file_id in dataset.combined_sounds: + creation_sound = dataset.combined_sounds[file_id] + creation_sound.add_reference(sound_raw_api_object) + + else: + creation_sound = CombinedSound(creation_sound_id, + file_id, + f"creation_sound_{creation_sound_id}", + dataset) + dataset.combined_sounds.update({file_id: creation_sound}) + creation_sound.add_reference(sound_raw_api_object) + + creation_sounds.append(creation_sound) + + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + creation_sounds, + "engine.util.sound.Sound") + + sound_forward_ref = ForwardRef(line, obj_name) + creatable_raw_api_object.add_raw_member("creation_sounds", + [sound_forward_ref], + "engine.util.create.CreatableGameEntity") + + line.add_raw_api_object(sound_raw_api_object) + + # Condition + unlock_conditions = [] + enabling_research_id = line.get_enabling_research_id() + if enabling_research_id > -1: + unlock_conditions.extend(AoCAuxiliarySubprocessor.get_condition(line, + obj_ref, + enabling_research_id)) + + creatable_raw_api_object.add_raw_member("condition", + unlock_conditions, + "engine.util.create.CreatableGameEntity") + + # Placement modes + placement_modes = [] + if isinstance(line, GenieBuildingLineGroup): + # Buildings are placed on the map + # Place mode + obj_name = f"{game_entity_name}.CreatableGameEntity.Place" + place_raw_api_object = RawAPIObject(obj_name, + "Place", + dataset.nyan_api_objects) + place_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Place") + place_location = ForwardRef(line, + f"{game_entity_name}.CreatableGameEntity") + place_raw_api_object.set_location(place_location) + + # Tile snap distance (uses 1.0 for grid placement) + place_raw_api_object.add_raw_member("tile_snap_distance", + 1.0, + "engine.util.placement_mode.type.Place") + # Clearance size + clearance_size_x = current_unit["clearance_size_x"].value + clearance_size_y = current_unit["clearance_size_y"].value + place_raw_api_object.add_raw_member("clearance_size_x", + clearance_size_x, + "engine.util.placement_mode.type.Place") + place_raw_api_object.add_raw_member("clearance_size_y", + clearance_size_y, + "engine.util.placement_mode.type.Place") + + # Allow rotation + place_raw_api_object.add_raw_member("allow_rotation", + True, + "engine.util.placement_mode.type.Place") + + # Max elevation difference + elevation_mode = current_unit["elevation_mode"].value + if elevation_mode == 2: + max_elevation_difference = 0 + + elif elevation_mode == 3: + max_elevation_difference = 1 + + else: + max_elevation_difference = MemberSpecialValue.NYAN_INF + + place_raw_api_object.add_raw_member("max_elevation_difference", + max_elevation_difference, + "engine.util.placement_mode.type.Place") + + line.add_raw_api_object(place_raw_api_object) + + place_forward_ref = ForwardRef(line, obj_name) + placement_modes.append(place_forward_ref) + + if line.get_class_id() == 39: + # Gates + obj_name = f"{game_entity_name}.CreatableGameEntity.Replace" + replace_raw_api_object = RawAPIObject(obj_name, + "Replace", + dataset.nyan_api_objects) + replace_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Replace") + replace_location = ForwardRef(line, + f"{game_entity_name}.CreatableGameEntity") + replace_raw_api_object.set_location(replace_location) + + # Game entities (only stone wall) + wall_line_id = 117 + wall_line = dataset.building_lines[wall_line_id] + wall_name = name_lookup_dict[117][0] + game_entities = [ForwardRef(wall_line, wall_name)] + replace_raw_api_object.add_raw_member("game_entities", + game_entities, + "engine.util.placement_mode.type.Replace") + + line.add_raw_api_object(replace_raw_api_object) + + replace_forward_ref = ForwardRef(line, obj_name) + placement_modes.append(replace_forward_ref) + + else: + placement_modes.append( + dataset.nyan_api_objects["engine.util.placement_mode.type.Eject"]) + + # OwnStorage mode + obj_name = f"{game_entity_name}.CreatableGameEntity.OwnStorage" + own_storage_raw_api_object = RawAPIObject(obj_name, "OwnStorage", + dataset.nyan_api_objects) + own_storage_raw_api_object.add_raw_parent("engine.util.placement_mode.type.OwnStorage") + own_storage_location = ForwardRef(line, + f"{game_entity_name}.CreatableGameEntity") + own_storage_raw_api_object.set_location(own_storage_location) + + # Container + container_forward_ref = ForwardRef(train_location, + (f"{train_location_name}.Storage." + f"{train_location_name}Container")) + own_storage_raw_api_object.add_raw_member("container", + container_forward_ref, + "engine.util.placement_mode.type.OwnStorage") + + line.add_raw_api_object(own_storage_raw_api_object) + + own_storage_forward_ref = ForwardRef(line, obj_name) + placement_modes.append(own_storage_forward_ref) + + creatable_raw_api_object.add_raw_member("placement_modes", + placement_modes, + "engine.util.create.CreatableGameEntity") + + line.add_raw_api_object(creatable_raw_api_object) + line.add_raw_api_object(cost_raw_api_object) diff --git a/openage/convert/processor/conversion/swgbcc/auxiliary/researchable_tech.py b/openage/convert/processor/conversion/swgbcc/auxiliary/researchable_tech.py new file mode 100644 index 0000000000..c0e8ef330c --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/auxiliary/researchable_tech.py @@ -0,0 +1,184 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for researchables (techs). +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup + + +def get_researchable_tech(tech_group: GenieTechEffectBundleGroup) -> None: + """ + Creates the ResearchableTech object for a Tech. + + :param tech_group: Tech group that is a technology. + """ + dataset = tech_group.data + research_location_id = tech_group.get_research_location_id() + research_location = dataset.building_lines[research_location_id] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + research_location_name = name_lookup_dict[research_location_id][0] + tech_name = tech_lookup_dict[tech_group.get_id()][0] + + obj_ref = f"{tech_name}.ResearchableTech" + obj_name = f"{tech_name}Researchable" + researchable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) + researchable_raw_api_object.add_raw_parent("engine.util.research.ResearchableTech") + + # Location of the object depends on whether it'a a unique tech or a normal tech + if tech_group.is_unique(): + # Add object to the Civ object + civ_id = tech_group.get_civilization() + civ = dataset.civ_groups[civ_id] + civ_name = civ_lookup_dict[civ_id][0] + + researchable_location = ForwardRef(civ, civ_name) + + else: + # Add object to the research location's Research ability + researchable_location = ForwardRef(research_location, + f"{research_location_name}.Research") + + researchable_raw_api_object.set_location(researchable_location) + + # Tech + tech_forward_ref = ForwardRef(tech_group, tech_name) + researchable_raw_api_object.add_raw_member("tech", + tech_forward_ref, + "engine.util.research.ResearchableTech") + + # Cost + cost_ref = f"{tech_name}.ResearchableTech.{tech_name}Cost" + cost_raw_api_object = RawAPIObject(cost_ref, + f"{tech_name}Cost", + dataset.nyan_api_objects) + cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") + tech_forward_ref = ForwardRef(tech_group, obj_ref) + cost_raw_api_object.set_location(tech_forward_ref) + + payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] + cost_raw_api_object.add_raw_member("payment_mode", + payment_mode, + "engine.util.cost.Cost") + + cost_amounts = [] + for resource_amount in tech_group.tech["research_resource_costs"].value: + resource_id = resource_amount["type_id"].value + + resource = None + resource_name = "" + if resource_id == -1: + # Not a valid resource + continue + + if resource_id == 0: + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + resource_name = "Food" + + elif resource_id == 1: + resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object( + ) + resource_name = "Carbon" + + elif resource_id == 2: + resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object() + resource_name = "Ore" + + elif resource_id == 3: + resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() + resource_name = "Nova" + + else: + # Other resource ids are handled differently + continue + + # Skip resources that are only expected to be there + if not resource_amount["enabled"].value: + continue + + amount = resource_amount["amount"].value + + cost_amount_ref = f"{cost_ref}.{resource_name}Amount" + cost_amount = RawAPIObject(cost_amount_ref, + f"{resource_name}Amount", + dataset.nyan_api_objects) + cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") + cost_forward_ref = ForwardRef(tech_group, cost_ref) + cost_amount.set_location(cost_forward_ref) + + cost_amount.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + cost_amount.add_raw_member("amount", + amount, + "engine.util.resource.ResourceAmount") + + cost_amount_forward_ref = ForwardRef(tech_group, cost_amount_ref) + cost_amounts.append(cost_amount_forward_ref) + tech_group.add_raw_api_object(cost_amount) + + cost_raw_api_object.add_raw_member("amount", + cost_amounts, + "engine.util.cost.type.ResourceCost") + + cost_forward_ref = ForwardRef(tech_group, cost_ref) + researchable_raw_api_object.add_raw_member("cost", + cost_forward_ref, + "engine.util.research.ResearchableTech") + + research_time = tech_group.tech["research_time"].value + + researchable_raw_api_object.add_raw_member("research_time", + research_time, + "engine.util.research.ResearchableTech") + + # Create sound object + sound_ref = f"{tech_name}.ResearchableTech.Sound" + sound_raw_api_object = RawAPIObject(sound_ref, "ResearchSound", + dataset.nyan_api_objects) + sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") + sound_location = ForwardRef(tech_group, + f"{tech_name}.ResearchableTech") + sound_raw_api_object.set_location(sound_location) + + # AoE doesn't support sounds here, so this is empty + sound_raw_api_object.add_raw_member("play_delay", + 0, + "engine.util.sound.Sound") + sound_raw_api_object.add_raw_member("sounds", + [], + "engine.util.sound.Sound") + + sound_forward_ref = ForwardRef(tech_group, sound_ref) + researchable_raw_api_object.add_raw_member("research_sounds", + [sound_forward_ref], + "engine.util.research.ResearchableTech") + + tech_group.add_raw_api_object(sound_raw_api_object) + + # Condition + unlock_conditions = [] + if tech_group.get_id() > -1: + unlock_conditions.extend(AoCAuxiliarySubprocessor.get_condition(tech_group, + obj_ref, + tech_group.get_id(), + top_level=True)) + + researchable_raw_api_object.add_raw_member("condition", + unlock_conditions, + "engine.util.research.ResearchableTech") + + tech_group.add_raw_api_object(researchable_raw_api_object) + tech_group.add_raw_api_object(cost_raw_api_object) diff --git a/openage/convert/processor/conversion/swgbcc/auxiliary_subprocessor.py b/openage/convert/processor/conversion/swgbcc/auxiliary_subprocessor.py index 3dc27666aa..59e0fc55c3 100644 --- a/openage/convert/processor/conversion/swgbcc/auxiliary_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/auxiliary_subprocessor.py @@ -1,29 +1,11 @@ # Copyright 2020-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-branches,too-many-statements -# -# TODO: -# pylint: disable=line-too-long """ Derives complex auxiliary objects from unit lines, techs or other objects. """ -from __future__ import annotations -import typing - -from .....nyan.nyan_structs import MemberSpecialValue -from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ - GenieBuildingLineGroup, GenieUnitLineGroup -from ....entity_object.conversion.combined_sound import CombinedSound -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ..aoc.auxiliary_subprocessor import AoCAuxiliarySubprocessor - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup - from openage.convert.entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .auxiliary.creatable_game_entity import get_creatable_game_entity +from .auxiliary.researchable_tech import get_researchable_tech class SWGBCCAuxiliarySubprocessor: @@ -31,545 +13,5 @@ class SWGBCCAuxiliarySubprocessor: Creates complexer auxiliary raw API objects for abilities in SWGB. """ - @staticmethod - def get_creatable_game_entity(line: GenieGameEntityGroup) -> None: - """ - Creates the CreatableGameEntity object for a unit/building line. - - :param line: Unit/Building line. - :type line: ...dataformat.converter_object.ConverterObjectGroup - """ - if isinstance(line, GenieVillagerGroup): - current_unit = line.variants[0].line[0] - - else: - current_unit = line.line[0] - - current_unit_id = line.get_head_unit_id() - dataset = line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - obj_ref = f"{game_entity_name}.CreatableGameEntity" - obj_name = f"{game_entity_name}Creatable" - creatable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) - creatable_raw_api_object.add_raw_parent("engine.util.create.CreatableGameEntity") - - # Get train location of line - train_location_id = line.get_train_location_id() - if isinstance(line, GenieBuildingLineGroup): - train_location = dataset.unit_lines[train_location_id] - train_location_name = name_lookup_dict[train_location_id][0] - - else: - train_location = dataset.building_lines[train_location_id] - train_location_name = name_lookup_dict[train_location_id][0] - - # Location of the object depends on whether it'a a unique unit or a normal unit - if line.is_unique(): - # Add object to the Civ object - enabling_research_id = line.get_enabling_research_id() - enabling_research = dataset.genie_techs[enabling_research_id] - enabling_civ_id = enabling_research["civilization_id"].value - - civ = dataset.civ_groups[enabling_civ_id] - civ_name = civ_lookup_dict[enabling_civ_id][0] - - creatable_location = ForwardRef(civ, civ_name) - - else: - # Add object to the train location's Create ability - creatable_location = ForwardRef(train_location, - f"{train_location_name}.Create") - - creatable_raw_api_object.set_location(creatable_location) - - # Game Entity - game_entity_forward_ref = ForwardRef(line, game_entity_name) - creatable_raw_api_object.add_raw_member("game_entity", - game_entity_forward_ref, - "engine.util.create.CreatableGameEntity") - - # TODO: Variants - variants_set = [] - - creatable_raw_api_object.add_raw_member("variants", variants_set, - "engine.util.create.CreatableGameEntity") - - # Cost (construction) - cost_name = f"{game_entity_name}.CreatableGameEntity.{game_entity_name}Cost" - cost_raw_api_object = RawAPIObject(cost_name, - f"{game_entity_name}Cost", - dataset.nyan_api_objects) - cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") - creatable_forward_ref = ForwardRef(line, obj_ref) - cost_raw_api_object.set_location(creatable_forward_ref) - - payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] - cost_raw_api_object.add_raw_member("payment_mode", - payment_mode, - "engine.util.cost.Cost") - - if line.is_repairable(): - # Cost (repair) for buildings - cost_repair_name = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}RepairCost") - cost_repair_raw_api_object = RawAPIObject(cost_repair_name, - f"{game_entity_name}RepairCost", - dataset.nyan_api_objects) - cost_repair_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") - creatable_forward_ref = ForwardRef(line, obj_ref) - cost_repair_raw_api_object.set_location(creatable_forward_ref) - - payment_repair_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Adaptive"] - cost_repair_raw_api_object.add_raw_member("payment_mode", - payment_repair_mode, - "engine.util.cost.Cost") - line.add_raw_api_object(cost_repair_raw_api_object) - - cost_amounts = [] - cost_repair_amounts = [] - for resource_amount in current_unit["resource_cost"].value: - resource_id = resource_amount["type_id"].value - - resource = None - resource_name = "" - if resource_id == -1: - # Not a valid resource - continue - - if resource_id == 0: - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - resource_name = "Food" - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object( - ) - resource_name = "Carbon" - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object() - resource_name = "Ore" - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() - resource_name = "Nova" - - else: - # Other resource ids are handled differently - continue - - # Skip resources that are only expected to be there - if not resource_amount["enabled"].value: - continue - - amount = resource_amount["amount"].value - - cost_amount_name = f"{cost_name}.{resource_name}Amount" - cost_amount = RawAPIObject(cost_amount_name, - f"{resource_name}Amount", - dataset.nyan_api_objects) - cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") - cost_forward_ref = ForwardRef(line, cost_name) - cost_amount.set_location(cost_forward_ref) - - cost_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - cost_amount.add_raw_member("amount", - amount, - "engine.util.resource.ResourceAmount") - - cost_amount_forward_ref = ForwardRef(line, cost_amount_name) - cost_amounts.append(cost_amount_forward_ref) - line.add_raw_api_object(cost_amount) - - if line.is_repairable(): - # Cost for repairing = half of the construction cost - cost_amount_name = f"{cost_repair_name}.{resource_name}Amount" - cost_amount = RawAPIObject(cost_amount_name, - f"{resource_name}Amount", - dataset.nyan_api_objects) - cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") - cost_forward_ref = ForwardRef(line, cost_repair_name) - cost_amount.set_location(cost_forward_ref) - - cost_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - cost_amount.add_raw_member("amount", - amount / 2, - "engine.util.resource.ResourceAmount") - - cost_amount_forward_ref = ForwardRef(line, cost_amount_name) - cost_repair_amounts.append(cost_amount_forward_ref) - line.add_raw_api_object(cost_amount) - - cost_raw_api_object.add_raw_member("amount", - cost_amounts, - "engine.util.cost.type.ResourceCost") - - if line.is_repairable(): - cost_repair_raw_api_object.add_raw_member("amount", - cost_repair_amounts, - "engine.util.cost.type.ResourceCost") - - cost_forward_ref = ForwardRef(line, cost_name) - creatable_raw_api_object.add_raw_member("cost", - cost_forward_ref, - "engine.util.create.CreatableGameEntity") - # Creation time - if isinstance(line, GenieUnitLineGroup): - creation_time = current_unit["creation_time"].value - - else: - # Buildings are created immediately - creation_time = 0 - - creatable_raw_api_object.add_raw_member("creation_time", - creation_time, - "engine.util.create.CreatableGameEntity") - - # Creation sound - creation_sound_id = current_unit["train_sound_id"].value - - # Create sound object - obj_name = f"{game_entity_name}.CreatableGameEntity.Sound" - sound_raw_api_object = RawAPIObject(obj_name, "CreationSound", - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(line, obj_ref) - sound_raw_api_object.set_location(sound_location) - - # Search for the sound if it exists - creation_sounds = [] - if creation_sound_id > -1: - genie_sound = dataset.genie_sounds[creation_sound_id] - file_ids = genie_sound.get_sounds(civ_id=-1) - - if file_ids: - file_id = genie_sound.get_sounds(civ_id=-1)[0] - - if file_id in dataset.combined_sounds: - creation_sound = dataset.combined_sounds[file_id] - creation_sound.add_reference(sound_raw_api_object) - - else: - creation_sound = CombinedSound(creation_sound_id, - file_id, - f"creation_sound_{creation_sound_id}", - dataset) - dataset.combined_sounds.update({file_id: creation_sound}) - creation_sound.add_reference(sound_raw_api_object) - - creation_sounds.append(creation_sound) - - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - creation_sounds, - "engine.util.sound.Sound") - - sound_forward_ref = ForwardRef(line, obj_name) - creatable_raw_api_object.add_raw_member("creation_sounds", - [sound_forward_ref], - "engine.util.create.CreatableGameEntity") - - line.add_raw_api_object(sound_raw_api_object) - - # Condition - unlock_conditions = [] - enabling_research_id = line.get_enabling_research_id() - if enabling_research_id > -1: - unlock_conditions.extend(AoCAuxiliarySubprocessor.get_condition(line, - obj_ref, - enabling_research_id)) - - creatable_raw_api_object.add_raw_member("condition", - unlock_conditions, - "engine.util.create.CreatableGameEntity") - - # Placement modes - placement_modes = [] - if isinstance(line, GenieBuildingLineGroup): - # Buildings are placed on the map - # Place mode - obj_name = f"{game_entity_name}.CreatableGameEntity.Place" - place_raw_api_object = RawAPIObject(obj_name, - "Place", - dataset.nyan_api_objects) - place_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Place") - place_location = ForwardRef(line, - f"{game_entity_name}.CreatableGameEntity") - place_raw_api_object.set_location(place_location) - - # Tile snap distance (uses 1.0 for grid placement) - place_raw_api_object.add_raw_member("tile_snap_distance", - 1.0, - "engine.util.placement_mode.type.Place") - # Clearance size - clearance_size_x = current_unit["clearance_size_x"].value - clearance_size_y = current_unit["clearance_size_y"].value - place_raw_api_object.add_raw_member("clearance_size_x", - clearance_size_x, - "engine.util.placement_mode.type.Place") - place_raw_api_object.add_raw_member("clearance_size_y", - clearance_size_y, - "engine.util.placement_mode.type.Place") - - # Allow rotation - place_raw_api_object.add_raw_member("allow_rotation", - True, - "engine.util.placement_mode.type.Place") - - # Max elevation difference - elevation_mode = current_unit["elevation_mode"].value - if elevation_mode == 2: - max_elevation_difference = 0 - - elif elevation_mode == 3: - max_elevation_difference = 1 - - else: - max_elevation_difference = MemberSpecialValue.NYAN_INF - - place_raw_api_object.add_raw_member("max_elevation_difference", - max_elevation_difference, - "engine.util.placement_mode.type.Place") - - line.add_raw_api_object(place_raw_api_object) - - place_forward_ref = ForwardRef(line, obj_name) - placement_modes.append(place_forward_ref) - - if line.get_class_id() == 39: - # Gates - obj_name = f"{game_entity_name}.CreatableGameEntity.Replace" - replace_raw_api_object = RawAPIObject(obj_name, - "Replace", - dataset.nyan_api_objects) - replace_raw_api_object.add_raw_parent("engine.util.placement_mode.type.Replace") - replace_location = ForwardRef(line, - f"{game_entity_name}.CreatableGameEntity") - replace_raw_api_object.set_location(replace_location) - - # Game entities (only stone wall) - wall_line_id = 117 - wall_line = dataset.building_lines[wall_line_id] - wall_name = name_lookup_dict[117][0] - game_entities = [ForwardRef(wall_line, wall_name)] - replace_raw_api_object.add_raw_member("game_entities", - game_entities, - "engine.util.placement_mode.type.Replace") - - line.add_raw_api_object(replace_raw_api_object) - - replace_forward_ref = ForwardRef(line, obj_name) - placement_modes.append(replace_forward_ref) - - else: - placement_modes.append( - dataset.nyan_api_objects["engine.util.placement_mode.type.Eject"]) - - # OwnStorage mode - obj_name = f"{game_entity_name}.CreatableGameEntity.OwnStorage" - own_storage_raw_api_object = RawAPIObject(obj_name, "OwnStorage", - dataset.nyan_api_objects) - own_storage_raw_api_object.add_raw_parent("engine.util.placement_mode.type.OwnStorage") - own_storage_location = ForwardRef(line, - f"{game_entity_name}.CreatableGameEntity") - own_storage_raw_api_object.set_location(own_storage_location) - - # Container - container_forward_ref = ForwardRef(train_location, - (f"{train_location_name}.Storage." - f"{train_location_name}Container")) - own_storage_raw_api_object.add_raw_member("container", - container_forward_ref, - "engine.util.placement_mode.type.OwnStorage") - - line.add_raw_api_object(own_storage_raw_api_object) - - own_storage_forward_ref = ForwardRef(line, obj_name) - placement_modes.append(own_storage_forward_ref) - - creatable_raw_api_object.add_raw_member("placement_modes", - placement_modes, - "engine.util.create.CreatableGameEntity") - - line.add_raw_api_object(creatable_raw_api_object) - line.add_raw_api_object(cost_raw_api_object) - - @staticmethod - def get_researchable_tech(tech_group: GenieTechEffectBundleGroup) -> None: - """ - Creates the ResearchableTech object for a Tech. - - :param tech_group: Tech group that is a technology. - :type tech_group: ...dataformat.converter_object.ConverterObjectGroup - """ - dataset = tech_group.data - research_location_id = tech_group.get_research_location_id() - research_location = dataset.building_lines[research_location_id] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - research_location_name = name_lookup_dict[research_location_id][0] - tech_name = tech_lookup_dict[tech_group.get_id()][0] - - obj_ref = f"{tech_name}.ResearchableTech" - obj_name = f"{tech_name}Researchable" - researchable_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) - researchable_raw_api_object.add_raw_parent("engine.util.research.ResearchableTech") - - # Location of the object depends on whether it'a a unique tech or a normal tech - if tech_group.is_unique(): - # Add object to the Civ object - civ_id = tech_group.get_civilization() - civ = dataset.civ_groups[civ_id] - civ_name = civ_lookup_dict[civ_id][0] - - researchable_location = ForwardRef(civ, civ_name) - - else: - # Add object to the research location's Research ability - researchable_location = ForwardRef(research_location, - f"{research_location_name}.Research") - - researchable_raw_api_object.set_location(researchable_location) - - # Tech - tech_forward_ref = ForwardRef(tech_group, tech_name) - researchable_raw_api_object.add_raw_member("tech", - tech_forward_ref, - "engine.util.research.ResearchableTech") - - # Cost - cost_ref = f"{tech_name}.ResearchableTech.{tech_name}Cost" - cost_raw_api_object = RawAPIObject(cost_ref, - f"{tech_name}Cost", - dataset.nyan_api_objects) - cost_raw_api_object.add_raw_parent("engine.util.cost.type.ResourceCost") - tech_forward_ref = ForwardRef(tech_group, obj_ref) - cost_raw_api_object.set_location(tech_forward_ref) - - payment_mode = dataset.nyan_api_objects["engine.util.payment_mode.type.Advance"] - cost_raw_api_object.add_raw_member("payment_mode", - payment_mode, - "engine.util.cost.Cost") - - cost_amounts = [] - for resource_amount in tech_group.tech["research_resource_costs"].value: - resource_id = resource_amount["type_id"].value - - resource = None - resource_name = "" - if resource_id == -1: - # Not a valid resource - continue - - if resource_id == 0: - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - resource_name = "Food" - - elif resource_id == 1: - resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object( - ) - resource_name = "Carbon" - - elif resource_id == 2: - resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object() - resource_name = "Ore" - - elif resource_id == 3: - resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() - resource_name = "Nova" - - else: - # Other resource ids are handled differently - continue - - # Skip resources that are only expected to be there - if not resource_amount["enabled"].value: - continue - - amount = resource_amount["amount"].value - - cost_amount_ref = f"{cost_ref}.{resource_name}Amount" - cost_amount = RawAPIObject(cost_amount_ref, - f"{resource_name}Amount", - dataset.nyan_api_objects) - cost_amount.add_raw_parent("engine.util.resource.ResourceAmount") - cost_forward_ref = ForwardRef(tech_group, cost_ref) - cost_amount.set_location(cost_forward_ref) - - cost_amount.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - cost_amount.add_raw_member("amount", - amount, - "engine.util.resource.ResourceAmount") - - cost_amount_forward_ref = ForwardRef(tech_group, cost_amount_ref) - cost_amounts.append(cost_amount_forward_ref) - tech_group.add_raw_api_object(cost_amount) - - cost_raw_api_object.add_raw_member("amount", - cost_amounts, - "engine.util.cost.type.ResourceCost") - - cost_forward_ref = ForwardRef(tech_group, cost_ref) - researchable_raw_api_object.add_raw_member("cost", - cost_forward_ref, - "engine.util.research.ResearchableTech") - - research_time = tech_group.tech["research_time"].value - - researchable_raw_api_object.add_raw_member("research_time", - research_time, - "engine.util.research.ResearchableTech") - - # Create sound object - sound_ref = f"{tech_name}.ResearchableTech.Sound" - sound_raw_api_object = RawAPIObject(sound_ref, "ResearchSound", - dataset.nyan_api_objects) - sound_raw_api_object.add_raw_parent("engine.util.sound.Sound") - sound_location = ForwardRef(tech_group, - f"{tech_name}.ResearchableTech") - sound_raw_api_object.set_location(sound_location) - - # AoE doesn't support sounds here, so this is empty - sound_raw_api_object.add_raw_member("play_delay", - 0, - "engine.util.sound.Sound") - sound_raw_api_object.add_raw_member("sounds", - [], - "engine.util.sound.Sound") - - sound_forward_ref = ForwardRef(tech_group, sound_ref) - researchable_raw_api_object.add_raw_member("research_sounds", - [sound_forward_ref], - "engine.util.research.ResearchableTech") - - tech_group.add_raw_api_object(sound_raw_api_object) - - # Condition - unlock_conditions = [] - if tech_group.get_id() > -1: - unlock_conditions.extend(AoCAuxiliarySubprocessor.get_condition(tech_group, - obj_ref, - tech_group.get_id(), - top_level=True)) - - researchable_raw_api_object.add_raw_member("condition", - unlock_conditions, - "engine.util.research.ResearchableTech") - - tech_group.add_raw_api_object(researchable_raw_api_object) - tech_group.add_raw_api_object(cost_raw_api_object) + get_creatable_game_entity = staticmethod(get_creatable_game_entity) + get_researchable_tech = staticmethod(get_researchable_tech) From 12e7d803de0176d5db7f92c839087e59378843b5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 14:28:45 +0200 Subject: [PATCH 147/163] convert: Refactor SWBGCCCivSubprocessor into separate files. --- .../conversion/swgbcc/CMakeLists.txt | 1 + .../conversion/swgbcc/civ/CMakeLists.txt | 5 + .../conversion/swgbcc/civ/__init__.py | 5 + .../conversion/swgbcc/civ/modifiers.py | 26 +++ .../swgbcc/civ/starting_resources.py | 139 ++++++++++++++++ .../conversion/swgbcc/civ_subprocessor.py | 150 +----------------- 6 files changed, 183 insertions(+), 143 deletions(-) create mode 100644 openage/convert/processor/conversion/swgbcc/civ/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/civ/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/civ/modifiers.py create mode 100644 openage/convert/processor/conversion/swgbcc/civ/starting_resources.py diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index 2a4ee4c7e8..cf9300dd57 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -14,3 +14,4 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(auxiliary) +add_subdirectory(civ) diff --git a/openage/convert/processor/conversion/swgbcc/civ/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/civ/CMakeLists.txt new file mode 100644 index 0000000000..7b6301b4ad --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/civ/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py + modifiers.py + starting_resources.py +) diff --git a/openage/convert/processor/conversion/swgbcc/civ/__init__.py b/openage/convert/processor/conversion/swgbcc/civ/__init__.py new file mode 100644 index 0000000000..4c766c2781 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/civ/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches and modifiers for civs. +""" diff --git a/openage/convert/processor/conversion/swgbcc/civ/modifiers.py b/openage/convert/processor/conversion/swgbcc/civ/modifiers.py new file mode 100644 index 0000000000..37dffdb32c --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/civ/modifiers.py @@ -0,0 +1,26 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ modifiers. +""" +from __future__ import annotations +import typing + + +if typing.TYPE_CHECKING: + from .....value_object.conversion.forward_ref import ForwardRef + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def get_modifiers(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Returns global modifiers of a civ. + """ + modifiers = [] + + for civ_bonus in civ_group.civ_boni.values(): + if civ_bonus.replaces_researchable_tech(): + # TODO: instant tech research modifier + pass + + return modifiers diff --git a/openage/convert/processor/conversion/swgbcc/civ/starting_resources.py b/openage/convert/processor/conversion/swgbcc/civ/starting_resources.py new file mode 100644 index 0000000000..d267fb27f8 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/civ/starting_resources.py @@ -0,0 +1,139 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +nyan conversion routines for civ starting resources. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def get_starting_resources(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: + """ + Returns the starting resources of a civ. + """ + resource_amounts = [] + + civ_id = civ_group.get_id() + dataset = civ_group.data + + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + civ_name = civ_lookup_dict[civ_id][0] + + # Find starting resource amounts + food_amount = civ_group.civ["resources"][91].value + carbon_amount = civ_group.civ["resources"][92].value + nova_amount = civ_group.civ["resources"][93].value + ore_amount = civ_group.civ["resources"][94].value + + # Find civ unique starting resources + tech_tree = civ_group.get_tech_tree_effects() + for effect in tech_tree: + type_id = effect.get_type() + + if type_id != 1: + continue + + resource_id = effect["attr_a"].value + amount = effect["attr_d"].value + if resource_id == 91: + food_amount += amount + + elif resource_id == 92: + carbon_amount += amount + + elif resource_id == 93: + nova_amount += amount + + elif resource_id == 94: + ore_amount += amount + + food_ref = f"{civ_name}.FoodStartingAmount" + food_raw_api_object = RawAPIObject(food_ref, "FoodStartingAmount", + dataset.nyan_api_objects) + food_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + food_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() + food_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + food_raw_api_object.add_raw_member("amount", + food_amount, + "engine.util.resource.ResourceAmount") + + food_forward_ref = ForwardRef(civ_group, food_ref) + resource_amounts.append(food_forward_ref) + + carbon_ref = f"{civ_name}.CarbonStartingAmount" + carbon_raw_api_object = RawAPIObject(carbon_ref, "CarbonStartingAmount", + dataset.nyan_api_objects) + carbon_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + carbon_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object() + carbon_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + carbon_raw_api_object.add_raw_member("amount", + carbon_amount, + "engine.util.resource.ResourceAmount") + + carbon_forward_ref = ForwardRef(civ_group, carbon_ref) + resource_amounts.append(carbon_forward_ref) + + nova_ref = f"{civ_name}.NovaStartingAmount" + nova_raw_api_object = RawAPIObject(nova_ref, "NovaStartingAmount", + dataset.nyan_api_objects) + nova_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + nova_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() + nova_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + nova_raw_api_object.add_raw_member("amount", + nova_amount, + "engine.util.resource.ResourceAmount") + + nova_forward_ref = ForwardRef(civ_group, nova_ref) + resource_amounts.append(nova_forward_ref) + + ore_ref = f"{civ_name}.OreStartingAmount" + ore_raw_api_object = RawAPIObject(ore_ref, "OreStartingAmount", + dataset.nyan_api_objects) + ore_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") + civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) + ore_raw_api_object.set_location(civ_location) + + resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object() + ore_raw_api_object.add_raw_member("type", + resource, + "engine.util.resource.ResourceAmount") + + ore_raw_api_object.add_raw_member("amount", + ore_amount, + "engine.util.resource.ResourceAmount") + + ore_forward_ref = ForwardRef(civ_group, ore_ref) + resource_amounts.append(ore_forward_ref) + + civ_group.add_raw_api_object(food_raw_api_object) + civ_group.add_raw_api_object(carbon_raw_api_object) + civ_group.add_raw_api_object(nova_raw_api_object) + civ_group.add_raw_api_object(ore_raw_api_object) + + return resource_amounts diff --git a/openage/convert/processor/conversion/swgbcc/civ_subprocessor.py b/openage/convert/processor/conversion/swgbcc/civ_subprocessor.py index 320351d662..f61178d753 100644 --- a/openage/convert/processor/conversion/swgbcc/civ_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/civ_subprocessor.py @@ -8,15 +8,15 @@ from __future__ import annotations import typing -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef from ..aoc.civ_subprocessor import AoCCivSubprocessor from .tech_subprocessor import SWGBCCTechSubprocessor +from .civ.modifiers import get_modifiers +from .civ.starting_resources import get_starting_resources + if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from ....value_object.conversion.forward_ref import ForwardRef + from ....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup class SWGBCCCivSubprocessor: @@ -42,141 +42,5 @@ def get_civ_setup(cls, civ_group: GenieCivilizationGroup) -> list[ForwardRef]: return patches - @classmethod - def get_modifiers(cls, civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Returns global modifiers of a civ. - """ - modifiers = [] - - for civ_bonus in civ_group.civ_boni.values(): - if civ_bonus.replaces_researchable_tech(): - # TODO: instant tech research modifier - pass - - return modifiers - - @staticmethod - def get_starting_resources(civ_group: GenieCivilizationGroup) -> list[ForwardRef]: - """ - Returns the starting resources of a civ. - """ - resource_amounts = [] - - civ_id = civ_group.get_id() - dataset = civ_group.data - - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - civ_name = civ_lookup_dict[civ_id][0] - - # Find starting resource amounts - food_amount = civ_group.civ["resources"][91].value - carbon_amount = civ_group.civ["resources"][92].value - nova_amount = civ_group.civ["resources"][93].value - ore_amount = civ_group.civ["resources"][94].value - - # Find civ unique starting resources - tech_tree = civ_group.get_tech_tree_effects() - for effect in tech_tree: - type_id = effect.get_type() - - if type_id != 1: - continue - - resource_id = effect["attr_a"].value - amount = effect["attr_d"].value - if resource_id == 91: - food_amount += amount - - elif resource_id == 92: - carbon_amount += amount - - elif resource_id == 93: - nova_amount += amount - - elif resource_id == 94: - ore_amount += amount - - food_ref = f"{civ_name}.FoodStartingAmount" - food_raw_api_object = RawAPIObject(food_ref, "FoodStartingAmount", - dataset.nyan_api_objects) - food_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - food_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Food"].get_nyan_object() - food_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - food_raw_api_object.add_raw_member("amount", - food_amount, - "engine.util.resource.ResourceAmount") - - food_forward_ref = ForwardRef(civ_group, food_ref) - resource_amounts.append(food_forward_ref) - - carbon_ref = f"{civ_name}.CarbonStartingAmount" - carbon_raw_api_object = RawAPIObject(carbon_ref, "CarbonStartingAmount", - dataset.nyan_api_objects) - carbon_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - carbon_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Carbon"].get_nyan_object() - carbon_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - carbon_raw_api_object.add_raw_member("amount", - carbon_amount, - "engine.util.resource.ResourceAmount") - - carbon_forward_ref = ForwardRef(civ_group, carbon_ref) - resource_amounts.append(carbon_forward_ref) - - nova_ref = f"{civ_name}.NovaStartingAmount" - nova_raw_api_object = RawAPIObject(nova_ref, "NovaStartingAmount", - dataset.nyan_api_objects) - nova_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - nova_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Nova"].get_nyan_object() - nova_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - nova_raw_api_object.add_raw_member("amount", - nova_amount, - "engine.util.resource.ResourceAmount") - - nova_forward_ref = ForwardRef(civ_group, nova_ref) - resource_amounts.append(nova_forward_ref) - - ore_ref = f"{civ_name}.OreStartingAmount" - ore_raw_api_object = RawAPIObject(ore_ref, "OreStartingAmount", - dataset.nyan_api_objects) - ore_raw_api_object.add_raw_parent("engine.util.resource.ResourceAmount") - civ_location = ForwardRef(civ_group, civ_lookup_dict[civ_group.get_id()][0]) - ore_raw_api_object.set_location(civ_location) - - resource = dataset.pregen_nyan_objects["util.resource.types.Ore"].get_nyan_object() - ore_raw_api_object.add_raw_member("type", - resource, - "engine.util.resource.ResourceAmount") - - ore_raw_api_object.add_raw_member("amount", - ore_amount, - "engine.util.resource.ResourceAmount") - - ore_forward_ref = ForwardRef(civ_group, ore_ref) - resource_amounts.append(ore_forward_ref) - - civ_group.add_raw_api_object(food_raw_api_object) - civ_group.add_raw_api_object(carbon_raw_api_object) - civ_group.add_raw_api_object(nova_raw_api_object) - civ_group.add_raw_api_object(ore_raw_api_object) - - return resource_amounts + get_modifiers = staticmethod(get_modifiers) + get_starting_resources = staticmethod(get_starting_resources) From 5f3f4a415794a4a6b3c036d13034a73e184fd8a9 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 14:49:32 +0200 Subject: [PATCH 148/163] convert: Refactor SWGBCCNyanSubprocessor into separate files. --- .../conversion/swgbcc/CMakeLists.txt | 1 + .../conversion/swgbcc/nyan/CMakeLists.txt | 9 + .../conversion/swgbcc/nyan/__init__.py | 6 + .../conversion/swgbcc/nyan/ambient.py | 109 +++ .../conversion/swgbcc/nyan/building.py | 180 ++++ .../processor/conversion/swgbcc/nyan/civ.py | 138 +++ .../conversion/swgbcc/nyan/projectile.py | 93 ++ .../processor/conversion/swgbcc/nyan/tech.py | 140 +++ .../processor/conversion/swgbcc/nyan/unit.py | 241 ++++++ .../conversion/swgbcc/nyan_subprocessor.py | 815 +----------------- 10 files changed, 931 insertions(+), 801 deletions(-) create mode 100644 openage/convert/processor/conversion/swgbcc/nyan/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/nyan/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/nyan/ambient.py create mode 100644 openage/convert/processor/conversion/swgbcc/nyan/building.py create mode 100644 openage/convert/processor/conversion/swgbcc/nyan/civ.py create mode 100644 openage/convert/processor/conversion/swgbcc/nyan/projectile.py create mode 100644 openage/convert/processor/conversion/swgbcc/nyan/tech.py create mode 100644 openage/convert/processor/conversion/swgbcc/nyan/unit.py diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index cf9300dd57..812479d69d 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -15,3 +15,4 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(auxiliary) add_subdirectory(civ) +add_subdirectory(nyan) diff --git a/openage/convert/processor/conversion/swgbcc/nyan/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/nyan/CMakeLists.txt new file mode 100644 index 0000000000..d39689621c --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/nyan/CMakeLists.txt @@ -0,0 +1,9 @@ +add_py_modules( + __init__.py + ambient.py + building.py + civ.py + projectile.py + tech.py + unit.py +) diff --git a/openage/convert/processor/conversion/swgbcc/nyan/__init__.py b/openage/convert/processor/conversion/swgbcc/nyan/__init__.py new file mode 100644 index 0000000000..07ae6c5fd5 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/nyan/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert API-like objects to nyan objects. Subroutine of the +main SWGBCC processor. +""" diff --git a/openage/convert/processor/conversion/swgbcc/nyan/ambient.py b/openage/convert/processor/conversion/swgbcc/nyan/ambient.py new file mode 100644 index 0000000000..d240348f86 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/nyan/ambient.py @@ -0,0 +1,109 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert ambient groups to openage game entities. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ..ability_subprocessor import SWGBCCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieAmbientGroup + + +def ambient_group_to_game_entity(ambient_group: GenieAmbientGroup) -> None: + """ + Creates raw API objects for an ambient group. + + :param ambient_group: Unit line that gets converted to a game entity. + """ + ambient_unit = ambient_group.get_head_unit() + ambient_id = ambient_group.get_head_unit_id() + + dataset = ambient_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[ambient_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[ambient_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[ambient_id][1]) + ambient_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give an ambient the types + # - util.game_entity_type.types.Ambient + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Ambient"].get_nyan_object( + ) + types_set.append(type_obj) + + unit_class = ambient_unit["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + interaction_mode = ambient_unit["interaction_mode"].value + + if interaction_mode >= 0: + abilities_set.append(AoCAbilitySubprocessor.death_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.resistance_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(ambient_group)) + + if interaction_mode >= 2: + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(ambient_group)) + + if not ambient_group.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(ambient_group)) + + if ambient_group.is_harvestable(): + abilities_set.append(SWGBCCAbilitySubprocessor.harvestable_ability(ambient_group)) + + # ======================================================================= + # Abilities + # ======================================================================= + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers_set = [] + + raw_api_object.add_raw_member("modifiers", modifiers_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + variants_set = [] + + raw_api_object.add_raw_member("variants", variants_set, + "engine.util.game_entity.GameEntity") diff --git a/openage/convert/processor/conversion/swgbcc/nyan/building.py b/openage/convert/processor/conversion/swgbcc/nyan/building.py new file mode 100644 index 0000000000..941cd51b4e --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/nyan/building.py @@ -0,0 +1,180 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert building lines to openage game entities. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ..ability_subprocessor import SWGBCCAbilitySubprocessor +from ..auxiliary_subprocessor import SWGBCCAuxiliarySubprocessor +from .projectile import projectiles_from_line + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup + + +def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: + """ + Creates raw API objects for a building line. + + :param building_line: Building line that gets converted to a game entity. + """ + current_building = building_line.line[0] + current_building_id = building_line.get_head_unit_id() + dataset = building_line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[current_building_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[current_building_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[current_building_id][1]) + building_line.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give a building two types + # - util.game_entity_type.types.Building (if unit_type >= 80) + # - util.game_entity_type.types. (depending on the class) + # and additionally + # - util.game_entity_type.types.DropSite (only if this is used as a drop site) + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + unit_type = current_building["unit_type"].value + + if unit_type >= 80: + type_obj = dataset.pregen_nyan_objects[ + "util.game_entity_type.types.Building" + ].get_nyan_object() + types_set.append(type_obj) + + unit_class = current_building["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + if building_line.is_dropsite(): + type_obj = dataset.pregen_nyan_objects[ + "util.game_entity_type.types.DropSite" + ].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + abilities_set.append( + SWGBCCAbilitySubprocessor.attribute_change_tracker_ability(building_line)) + abilities_set.append(SWGBCCAbilitySubprocessor.death_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.resistance_ability(building_line)) + abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.stop_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(building_line)) + + # Config abilities + # if building_line.is_creatable(): + # abilities_set.append(SWGBCCAbilitySubprocessor.constructable_ability(building_line)) + + if not building_line.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(building_line)) + + if building_line.has_foundation(): + if building_line.get_class_id() == 7: + # Use OverlayTerrain for the farm terrain + abilities_set.append(AoCAbilitySubprocessor.overlay_terrain_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line, + terrain_id=7)) + + else: + abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line)) + + # Creation/Research abilities + if len(building_line.creates) > 0: + abilities_set.append(AoCAbilitySubprocessor.create_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.production_queue_ability(building_line)) + + if len(building_line.researches) > 0: + abilities_set.append(AoCAbilitySubprocessor.research_ability(building_line)) + + # Effect abilities + if building_line.is_projectile_shooter(): + abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(building_line, 7)) + abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(building_line)) + projectiles_from_line(building_line) + + # Storage abilities + if building_line.is_garrison(): + abilities_set.append(AoCAbilitySubprocessor.storage_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(building_line)) + + garrison_mode = building_line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.NATURAL: + abilities_set.append( + SWGBCCAbilitySubprocessor.send_back_to_task_ability(building_line)) + + if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): + abilities_set.append(AoCAbilitySubprocessor.rally_point_ability(building_line)) + + # Resource abilities + if building_line.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(building_line)) + + if building_line.is_dropsite(): + abilities_set.append(AoCAbilitySubprocessor.drop_site_ability(building_line)) + + ability = AoCAbilitySubprocessor.provide_contingent_ability(building_line) + if ability: + abilities_set.append(ability) + + # Trade abilities + if building_line.is_trade_post(): + abilities_set.append(SWGBCCAbilitySubprocessor.trade_post_ability(building_line)) + + if building_line.get_id() == 84: + # Spaceport trading + abilities_set.extend( + SWGBCCAbilitySubprocessor.exchange_resources_ability(building_line)) + + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Modifiers + # ======================================================================= + raw_api_object.add_raw_member("modifiers", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Misc (Objects that are not used by the unit line itself, but use its values) + # ======================================================================= + if building_line.is_creatable(): + SWGBCCAuxiliarySubprocessor.get_creatable_game_entity(building_line) diff --git a/openage/convert/processor/conversion/swgbcc/nyan/civ.py b/openage/convert/processor/conversion/swgbcc/nyan/civ.py new file mode 100644 index 0000000000..44c5cf47dd --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/nyan/civ.py @@ -0,0 +1,138 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert civ groups to openage player setups. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..civ_subprocessor import SWGBCCCivSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup + + +def civ_group_to_civ(civ_group: GenieCivilizationGroup) -> None: + """ + Creates raw API objects for a civ group. + + :param civ_group: Terrain group that gets converted to a tech. + """ + civ_id = civ_group.get_id() + + dataset = civ_group.data + + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + + # Start with the Tech object + tech_name = civ_lookup_dict[civ_id][0] + raw_api_object = RawAPIObject(tech_name, tech_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.setup.PlayerSetup") + + obj_location = f"data/civ/{civ_lookup_dict[civ_id][1]}/" + + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(civ_lookup_dict[civ_id][1]) + civ_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{tech_name}.{tech_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{tech_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(civ_group, tech_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(civ_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Description + # ======================================================================= + description_ref = f"{tech_name}.{tech_name}Description" + description_raw_api_object = RawAPIObject(description_ref, + f"{tech_name}Description", + dataset.nyan_api_objects) + description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + description_location = ForwardRef(civ_group, tech_name) + description_raw_api_object.set_location(description_location) + + description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + description_forward_ref = ForwardRef(civ_group, description_ref) + raw_api_object.add_raw_member("description", + description_forward_ref, + "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(description_raw_api_object) + + # ======================================================================= + # Long description + # ======================================================================= + long_description_ref = f"{tech_name}.{tech_name}LongDescription" + long_description_raw_api_object = RawAPIObject(long_description_ref, + f"{tech_name}LongDescription", + dataset.nyan_api_objects) + long_description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + long_description_location = ForwardRef(civ_group, tech_name) + long_description_raw_api_object.set_location(long_description_location) + + long_description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + long_description_forward_ref = ForwardRef(civ_group, long_description_ref) + raw_api_object.add_raw_member("long_description", + long_description_forward_ref, + "engine.util.setup.PlayerSetup") + civ_group.add_raw_api_object(long_description_raw_api_object) + + # ======================================================================= + # TODO: Leader names + # ======================================================================= + raw_api_object.add_raw_member("leader_names", + [], + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Modifiers + # ======================================================================= + modifiers = SWGBCCCivSubprocessor.get_modifiers(civ_group) + raw_api_object.add_raw_member("modifiers", + modifiers, + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Starting resources + # ======================================================================= + resource_amounts = SWGBCCCivSubprocessor.get_starting_resources(civ_group) + raw_api_object.add_raw_member("starting_resources", + resource_amounts, + "engine.util.setup.PlayerSetup") + + # ======================================================================= + # Game setup + # ======================================================================= + game_setup = SWGBCCCivSubprocessor.get_civ_setup(civ_group) + raw_api_object.add_raw_member("game_setup", + game_setup, + "engine.util.setup.PlayerSetup") diff --git a/openage/convert/processor/conversion/swgbcc/nyan/projectile.py b/openage/convert/processor/conversion/swgbcc/nyan/projectile.py new file mode 100644 index 0000000000..4fe7747703 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/nyan/projectile.py @@ -0,0 +1,93 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert projectiles to openage game entities. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + + +def projectiles_from_line(line: GenieGameEntityGroup): + """ + Creates Projectile(GameEntity) raw API objects for a unit/building line. + + :param line: Line for which the projectiles are extracted. + """ + current_unit = line.get_head_unit() + 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] + game_entity_filename = name_lookup_dict[current_unit_id][1] + + projectiles_location = f"data/game_entity/generic/{game_entity_filename}/projectiles/" + + projectile_indices = [] + projectile_primary = current_unit["projectile_id0"].value + if projectile_primary > -1: + projectile_indices.append(0) + + projectile_secondary = current_unit["projectile_id1"].value + if projectile_secondary > -1: + projectile_indices.append(1) + + for projectile_num in projectile_indices: + obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{projectile_num}" + obj_name = f"Projectile{str(projectile_num)}" + proj_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) + proj_raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + proj_raw_api_object.set_location(projectiles_location) + proj_raw_api_object.set_filename(f"{game_entity_filename}_projectiles") + + # ======================================================================= + # Types + # ======================================================================= + types_set = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Projectile"].get_nyan_object()] + proj_raw_api_object.add_raw_member( + "types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + abilities_set.append(AoCAbilitySubprocessor.projectile_ability( + line, position=projectile_num)) + abilities_set.append(AoCAbilitySubprocessor.move_projectile_ability( + line, position=projectile_num)) + abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( + line, 7, False, projectile_num)) + # TODO: Death, Despawn + proj_raw_api_object.add_raw_member( + "abilities", abilities_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Modifiers + # ======================================================================= + modifiers_set = [] + + # modifiers_set.append(AoCModifierSubprocessor.flyover_effect_modifier(line)) + # modifiers_set.extend(AoCModifierSubprocessor.elevation_attack_modifiers(line)) + + proj_raw_api_object.add_raw_member( + "modifiers", modifiers_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Variants + # ======================================================================= + variants_set = [] + proj_raw_api_object.add_raw_member( + "variants", variants_set, "engine.util.game_entity.GameEntity") + + line.add_raw_api_object(proj_raw_api_object) + + # TODO: Implement diffing of civ lines diff --git a/openage/convert/processor/conversion/swgbcc/nyan/tech.py b/openage/convert/processor/conversion/swgbcc/nyan/tech.py new file mode 100644 index 0000000000..0d9f53c3ef --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/nyan/tech.py @@ -0,0 +1,140 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert tech groups to openage techs. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import UnitLineUpgrade +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef +from ..auxiliary_subprocessor import SWGBCCAuxiliarySubprocessor +from ..tech_subprocessor import SWGBCCTechSubprocessor + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup + + +def tech_group_to_tech(tech_group: GenieTechEffectBundleGroup) -> None: + """ + Creates raw API objects for a tech group. + + :param tech_group: Tech group that gets converted to a tech. + """ + tech_id = tech_group.get_id() + + # Skip Tech Level 0 tech + if tech_id == 0: + return + + dataset = tech_group.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + + # Start with the Tech object + tech_name = tech_lookup_dict[tech_id][0] + raw_api_object = RawAPIObject(tech_name, tech_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.tech.Tech") + + if isinstance(tech_group, UnitLineUpgrade): + unit_line = dataset.unit_lines[tech_group.get_line_id()] + head_unit_id = unit_line.get_head_unit_id() + obj_location = f"data/game_entity/generic/{name_lookup_dict[head_unit_id][1]}/" + + else: + obj_location = f"data/tech/generic/{tech_lookup_dict[tech_id][1]}/" + + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(tech_lookup_dict[tech_id][1]) + tech_group.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Types + # ======================================================================= + raw_api_object.add_raw_member("types", [], "engine.util.tech.Tech") + + # ======================================================================= + # Name + # ======================================================================= + name_ref = f"{tech_name}.{tech_name}Name" + name_raw_api_object = RawAPIObject(name_ref, + f"{tech_name}Name", + dataset.nyan_api_objects) + name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") + name_location = ForwardRef(tech_group, tech_name) + name_raw_api_object.set_location(name_location) + + name_raw_api_object.add_raw_member("translations", + [], + "engine.util.language.translated.type.TranslatedString") + + name_forward_ref = ForwardRef(tech_group, name_ref) + raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.tech.Tech") + tech_group.add_raw_api_object(name_raw_api_object) + + # ======================================================================= + # Description + # ======================================================================= + description_ref = f"{tech_name}.{tech_name}Description" + description_raw_api_object = RawAPIObject(description_ref, + f"{tech_name}Description", + dataset.nyan_api_objects) + description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + description_location = ForwardRef(tech_group, tech_name) + description_raw_api_object.set_location(description_location) + + description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + description_forward_ref = ForwardRef(tech_group, description_ref) + raw_api_object.add_raw_member("description", + description_forward_ref, + "engine.util.tech.Tech") + tech_group.add_raw_api_object(description_raw_api_object) + + # ======================================================================= + # Long description + # ======================================================================= + long_description_ref = f"{tech_name}.{tech_name}LongDescription" + long_description_raw_api_object = RawAPIObject(long_description_ref, + f"{tech_name}LongDescription", + dataset.nyan_api_objects) + long_description_raw_api_object.add_raw_parent( + "engine.util.language.translated.type.TranslatedMarkupFile") + long_description_location = ForwardRef(tech_group, tech_name) + long_description_raw_api_object.set_location(long_description_location) + + long_description_raw_api_object.add_raw_member( + "translations", + [], + "engine.util.language.translated.type.TranslatedMarkupFile" + ) + + long_description_forward_ref = ForwardRef(tech_group, long_description_ref) + raw_api_object.add_raw_member("long_description", + long_description_forward_ref, + "engine.util.tech.Tech") + tech_group.add_raw_api_object(long_description_raw_api_object) + + # ======================================================================= + # Updates + # ======================================================================= + patches = [] + patches.extend(SWGBCCTechSubprocessor.get_patches(tech_group)) + raw_api_object.add_raw_member("updates", patches, "engine.util.tech.Tech") + + # ======================================================================= + # Misc (Objects that are not used by the tech group itself, but use its values) + # ======================================================================= + if tech_group.is_researchable(): + SWGBCCAuxiliarySubprocessor.get_researchable_tech(tech_group) + + # TODO: Implement civ line techs diff --git a/openage/convert/processor/conversion/swgbcc/nyan/unit.py b/openage/convert/processor/conversion/swgbcc/nyan/unit.py new file mode 100644 index 0000000000..827ad7384c --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/nyan/unit.py @@ -0,0 +1,241 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Convert unit lines to openage game entities. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ + GenieGarrisonMode, GenieMonkGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from ...aoc.ability_subprocessor import AoCAbilitySubprocessor +from ..ability_subprocessor import SWGBCCAbilitySubprocessor +from ..auxiliary_subprocessor import SWGBCCAuxiliarySubprocessor +from .projectile import projectiles_from_line + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup + + +def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: + """ + Creates raw API objects for a unit line. + + :param unit_line: Unit line that gets converted to a game entity. + """ + current_unit = unit_line.get_head_unit() + current_unit_id = unit_line.get_head_unit_id() + + dataset = unit_line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) + + # Start with the generic GameEntity + game_entity_name = name_lookup_dict[current_unit_id][0] + obj_location = f"data/game_entity/generic/{name_lookup_dict[current_unit_id][1]}/" + raw_api_object = RawAPIObject(game_entity_name, game_entity_name, + dataset.nyan_api_objects) + raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") + raw_api_object.set_location(obj_location) + raw_api_object.set_filename(name_lookup_dict[current_unit_id][1]) + unit_line.add_raw_api_object(raw_api_object) + + # ======================================================================= + # Game Entity Types + # ======================================================================= + # we give a unit two types + # - util.game_entity_type.types.Unit (if unit_type >= 70) + # - util.game_entity_type.types. (depending on the class) + # ======================================================================= + # Create or use existing auxiliary types + types_set = [] + unit_type = current_unit["unit_type"].value + + if unit_type >= 70: + type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object( + ) + types_set.append(type_obj) + + unit_class = current_unit["unit_class"].value + class_name = class_lookup_dict[unit_class] + class_obj_name = f"util.game_entity_type.types.{class_name}" + type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() + types_set.append(type_obj) + + raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Abilities + # ======================================================================= + abilities_set = [] + + abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) + abilities_set.append(SWGBCCAbilitySubprocessor.idle_ability(unit_line)) + abilities_set.append(SWGBCCAbilitySubprocessor.collision_ability(unit_line)) + abilities_set.append(SWGBCCAbilitySubprocessor.live_ability(unit_line)) + abilities_set.append(SWGBCCAbilitySubprocessor.los_ability(unit_line)) + abilities_set.append(SWGBCCAbilitySubprocessor.move_ability(unit_line)) + abilities_set.append(SWGBCCAbilitySubprocessor.named_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.resistance_ability(unit_line)) + abilities_set.extend(SWGBCCAbilitySubprocessor.selectable_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.stop_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(unit_line)) + abilities_set.append(SWGBCCAbilitySubprocessor.turn_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.visibility_ability(unit_line)) + + # Creation + if len(unit_line.creates) > 0: + abilities_set.append(AoCAbilitySubprocessor.create_ability(unit_line)) + + # Config + ability = AoCAbilitySubprocessor.use_contingent_ability(unit_line) + if ability: + abilities_set.append(ability) + + if unit_line.get_head_unit_id() in (8, 115, 180): + # Healing/Recharging attribute points (jedi/sith/berserk) + abilities_set.extend(SWGBCCAbilitySubprocessor.regenerate_attribute_ability(unit_line)) + + # Applying effects and shooting projectiles + if unit_line.is_projectile_shooter(): + abilities_set.append(SWGBCCAbilitySubprocessor.shoot_projectile_ability(unit_line, 7)) + projectiles_from_line(unit_line) + + elif unit_line.is_melee() or unit_line.is_ranged(): + if unit_line.has_command(7): + # Attack + abilities_set.append( + SWGBCCAbilitySubprocessor.apply_discrete_effect_ability( + unit_line, + 7, + unit_line.is_ranged() + ) + ) + + if unit_line.has_command(101): + # Build + abilities_set.append( + SWGBCCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 101, + unit_line.is_ranged() + ) + ) + + if unit_line.has_command(104): + # Convert + abilities_set.append( + SWGBCCAbilitySubprocessor.apply_discrete_effect_ability( + unit_line, + 104, + unit_line.is_ranged() + ) + ) + + if unit_line.has_command(105): + # Heal + abilities_set.append( + SWGBCCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 105, + unit_line.is_ranged() + ) + ) + + if unit_line.has_command(106): + # Repair + abilities_set.append( + SWGBCCAbilitySubprocessor.apply_continuous_effect_ability( + unit_line, + 106, + unit_line.is_ranged() + ) + ) + + # Formation/Stance + if not isinstance(unit_line, GenieVillagerGroup): + abilities_set.append(AoCAbilitySubprocessor.formation_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(unit_line)) + + # Storage abilities + if unit_line.is_garrison(): + abilities_set.append(AoCAbilitySubprocessor.storage_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(unit_line)) + + garrison_mode = unit_line.get_garrison_mode() + + if garrison_mode == GenieGarrisonMode.MONK: + abilities_set.append(AoCAbilitySubprocessor.collect_storage_ability(unit_line)) + + if len(unit_line.garrison_locations) > 0: + ability = AoCAbilitySubprocessor.enter_container_ability(unit_line) + if ability: + abilities_set.append(ability) + + ability = AoCAbilitySubprocessor.exit_container_ability(unit_line) + if ability: + abilities_set.append(ability) + + if isinstance(unit_line, GenieMonkGroup): + abilities_set.append(AoCAbilitySubprocessor.transfer_storage_ability(unit_line)) + + # Resource abilities + if unit_line.is_gatherer(): + abilities_set.append(AoCAbilitySubprocessor.drop_resources_ability(unit_line)) + abilities_set.extend(SWGBCCAbilitySubprocessor.gather_ability(unit_line)) + + # Resource storage + if unit_line.is_gatherer() or unit_line.has_command(111): + abilities_set.append(SWGBCCAbilitySubprocessor.resource_storage_ability(unit_line)) + + if isinstance(unit_line, GenieVillagerGroup): + # Farm restocking + abilities_set.append(AoCAbilitySubprocessor.restock_ability(unit_line, 50)) + + if unit_line.is_harvestable(): + abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(unit_line)) + + if unit_type == 70 and unit_line.get_class_id() not in (1, 4, 5): + # Excludes cannons and animals + abilities_set.append(AoCAbilitySubprocessor.herd_ability(unit_line)) + + if unit_line.has_command(107): + abilities_set.append(AoCAbilitySubprocessor.herdable_ability(unit_line)) + + # Trade abilities + if unit_line.has_command(111): + abilities_set.append(SWGBCCAbilitySubprocessor.trade_ability(unit_line)) + + # ======================================================================= + # TODO: Transform + # ======================================================================= + raw_api_object.add_raw_member("abilities", abilities_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Modifiers + # ======================================================================= + modifiers_set = [] + + raw_api_object.add_raw_member("modifiers", modifiers_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # TODO: Variants + # ======================================================================= + variants_set = [] + + raw_api_object.add_raw_member("variants", variants_set, + "engine.util.game_entity.GameEntity") + + # ======================================================================= + # Misc (Objects that are not used by the unit line itself, but use its values) + # ======================================================================= + if unit_line.is_creatable(): + SWGBCCAuxiliarySubprocessor.get_creatable_game_entity(unit_line) diff --git a/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py b/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py index b014629619..3e1dd0add2 100644 --- a/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py @@ -1,9 +1,4 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Convert API-like objects to nyan objects. Subroutine of the @@ -12,25 +7,16 @@ from __future__ import annotations import typing -from ....entity_object.conversion.aoc.genie_tech import UnitLineUpgrade -from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ - GenieGarrisonMode, GenieMonkGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef -from ..aoc.ability_subprocessor import AoCAbilitySubprocessor from ..aoc.nyan_subprocessor import AoCNyanSubprocessor -from .ability_subprocessor import SWGBCCAbilitySubprocessor -from .auxiliary_subprocessor import SWGBCCAuxiliarySubprocessor -from .civ_subprocessor import SWGBCCCivSubprocessor -from .tech_subprocessor import SWGBCCTechSubprocessor +from .nyan.ambient import ambient_group_to_game_entity +from .nyan.building import building_line_to_game_entity +from .nyan.civ import civ_group_to_civ +from .nyan.projectile import projectiles_from_line +from .nyan.tech import tech_group_to_tech +from .nyan.unit import unit_line_to_game_entity if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.aoc.genie_civ import GenieCivilizationGroup - from openage.convert.entity_object.conversion.aoc.genie_object_container import GenieObjectContainer - from openage.convert.entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieUnitLineGroup, \ - GenieBuildingLineGroup, GenieAmbientGroup + from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer class SWGBCCNyanSubprocessor: @@ -161,782 +147,9 @@ def _process_game_entities(cls, full_data_set: GenieObjectContainer) -> None: for civ_group in full_data_set.civ_groups.values(): cls.civ_group_to_civ(civ_group) - @staticmethod - def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: - """ - Creates raw API objects for a unit line. - - :param unit_line: Unit line that gets converted to a game entity. - :type unit_line: ..dataformat.converter_object.ConverterObjectGroup - """ - - current_unit = unit_line.get_head_unit() - current_unit_id = unit_line.get_head_unit_id() - - dataset = unit_line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[current_unit_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[current_unit_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[current_unit_id][1]) - unit_line.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give a unit two types - # - util.game_entity_type.types.Unit (if unit_type >= 70) - # - util.game_entity_type.types. (depending on the class) - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - unit_type = current_unit["unit_type"].value - - if unit_type >= 70: - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = current_unit["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) - abilities_set.append(SWGBCCAbilitySubprocessor.idle_ability(unit_line)) - abilities_set.append(SWGBCCAbilitySubprocessor.collision_ability(unit_line)) - abilities_set.append(SWGBCCAbilitySubprocessor.live_ability(unit_line)) - abilities_set.append(SWGBCCAbilitySubprocessor.los_ability(unit_line)) - abilities_set.append(SWGBCCAbilitySubprocessor.move_ability(unit_line)) - abilities_set.append(SWGBCCAbilitySubprocessor.named_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.resistance_ability(unit_line)) - abilities_set.extend(SWGBCCAbilitySubprocessor.selectable_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.stop_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(unit_line)) - abilities_set.append(SWGBCCAbilitySubprocessor.turn_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(unit_line)) - - # Creation - if len(unit_line.creates) > 0: - abilities_set.append(AoCAbilitySubprocessor.create_ability(unit_line)) - - # Config - ability = AoCAbilitySubprocessor.use_contingent_ability(unit_line) - if ability: - abilities_set.append(ability) - - if unit_line.get_head_unit_id() in (8, 115, 180): - # Healing/Recharging attribute points (jedi/sith/berserk) - abilities_set.extend(SWGBCCAbilitySubprocessor.regenerate_attribute_ability(unit_line)) - - # Applying effects and shooting projectiles - if unit_line.is_projectile_shooter(): - abilities_set.append(SWGBCCAbilitySubprocessor.shoot_projectile_ability(unit_line, 7)) - SWGBCCNyanSubprocessor.projectiles_from_line(unit_line) - - elif unit_line.is_melee() or unit_line.is_ranged(): - if unit_line.has_command(7): - # Attack - abilities_set.append(SWGBCCAbilitySubprocessor.apply_discrete_effect_ability(unit_line, - 7, - unit_line.is_ranged())) - - if unit_line.has_command(101): - # Build - abilities_set.append(SWGBCCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 101, - unit_line.is_ranged())) - - if unit_line.has_command(104): - # Convert - abilities_set.append(SWGBCCAbilitySubprocessor.apply_discrete_effect_ability(unit_line, - 104, - unit_line.is_ranged())) - - if unit_line.has_command(105): - # Heal - abilities_set.append(SWGBCCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 105, - unit_line.is_ranged())) - - if unit_line.has_command(106): - # Repair - abilities_set.append(SWGBCCAbilitySubprocessor.apply_continuous_effect_ability(unit_line, - 106, - unit_line.is_ranged())) - - # Formation/Stance - if not isinstance(unit_line, GenieVillagerGroup): - abilities_set.append(AoCAbilitySubprocessor.formation_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(unit_line)) - - # Storage abilities - if unit_line.is_garrison(): - abilities_set.append(AoCAbilitySubprocessor.storage_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(unit_line)) - - garrison_mode = unit_line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.MONK: - abilities_set.append(AoCAbilitySubprocessor.collect_storage_ability(unit_line)) - - if len(unit_line.garrison_locations) > 0: - ability = AoCAbilitySubprocessor.enter_container_ability(unit_line) - if ability: - abilities_set.append(ability) - - ability = AoCAbilitySubprocessor.exit_container_ability(unit_line) - if ability: - abilities_set.append(ability) - - if isinstance(unit_line, GenieMonkGroup): - abilities_set.append(AoCAbilitySubprocessor.transfer_storage_ability(unit_line)) - - # Resource abilities - if unit_line.is_gatherer(): - abilities_set.append(AoCAbilitySubprocessor.drop_resources_ability(unit_line)) - abilities_set.extend(SWGBCCAbilitySubprocessor.gather_ability(unit_line)) - - # Resource storage - if unit_line.is_gatherer() or unit_line.has_command(111): - abilities_set.append(SWGBCCAbilitySubprocessor.resource_storage_ability(unit_line)) - - if isinstance(unit_line, GenieVillagerGroup): - # Farm restocking - abilities_set.append(AoCAbilitySubprocessor.restock_ability(unit_line, 50)) - - if unit_line.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(unit_line)) - - if unit_type == 70 and unit_line.get_class_id() not in (1, 4, 5): - # Excludes cannons and animals - abilities_set.append(AoCAbilitySubprocessor.herd_ability(unit_line)) - - if unit_line.has_command(107): - abilities_set.append(AoCAbilitySubprocessor.herdable_ability(unit_line)) - - # Trade abilities - if unit_line.has_command(111): - abilities_set.append(SWGBCCAbilitySubprocessor.trade_ability(unit_line)) - - # ======================================================================= - # TODO: Transform - # ======================================================================= - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Modifiers - # ======================================================================= - modifiers_set = [] - - raw_api_object.add_raw_member("modifiers", modifiers_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - variants_set = [] - - raw_api_object.add_raw_member("variants", variants_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Misc (Objects that are not used by the unit line itself, but use its values) - # ======================================================================= - if unit_line.is_creatable(): - SWGBCCAuxiliarySubprocessor.get_creatable_game_entity(unit_line) - - @staticmethod - def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: - """ - Creates raw API objects for a building line. - - :param building_line: Building line that gets converted to a game entity. - :type building_line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_building = building_line.line[0] - current_building_id = building_line.get_head_unit_id() - dataset = building_line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[current_building_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[current_building_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[current_building_id][1]) - building_line.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give a building two types - # - util.game_entity_type.types.Building (if unit_type >= 80) - # - util.game_entity_type.types. (depending on the class) - # and additionally - # - util.game_entity_type.types.DropSite (only if this is used as a drop site) - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - unit_type = current_building["unit_type"].value - - if unit_type >= 80: - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = current_building["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - if building_line.is_dropsite(): - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.DropSite"].get_nyan_object( - ) - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - abilities_set.append( - SWGBCCAbilitySubprocessor.attribute_change_tracker_ability(building_line)) - abilities_set.append(SWGBCCAbilitySubprocessor.death_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.resistance_ability(building_line)) - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.stop_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(building_line)) - - # Config abilities - # if building_line.is_creatable(): - # abilities_set.append(SWGBCCAbilitySubprocessor.constructable_ability(building_line)) - - if not building_line.is_passable(): - abilities_set.append(AoCAbilitySubprocessor.pathable_ability(building_line)) - - if building_line.has_foundation(): - if building_line.get_class_id() == 7: - # Use OverlayTerrain for the farm terrain - abilities_set.append(AoCAbilitySubprocessor.overlay_terrain_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line, - terrain_id=7)) - - else: - abilities_set.append(AoCAbilitySubprocessor.foundation_ability(building_line)) - - # Creation/Research abilities - if len(building_line.creates) > 0: - abilities_set.append(AoCAbilitySubprocessor.create_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.production_queue_ability(building_line)) - - if len(building_line.researches) > 0: - abilities_set.append(AoCAbilitySubprocessor.research_ability(building_line)) - - # Effect abilities - if building_line.is_projectile_shooter(): - abilities_set.append(AoCAbilitySubprocessor.shoot_projectile_ability(building_line, 7)) - abilities_set.append(AoCAbilitySubprocessor.game_entity_stance_ability(building_line)) - SWGBCCNyanSubprocessor.projectiles_from_line(building_line) - - # Storage abilities - if building_line.is_garrison(): - abilities_set.append(AoCAbilitySubprocessor.storage_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.remove_storage_ability(building_line)) - - garrison_mode = building_line.get_garrison_mode() - - if garrison_mode == GenieGarrisonMode.NATURAL: - abilities_set.append( - SWGBCCAbilitySubprocessor.send_back_to_task_ability(building_line)) - - if garrison_mode in (GenieGarrisonMode.NATURAL, GenieGarrisonMode.SELF_PRODUCED): - abilities_set.append(AoCAbilitySubprocessor.rally_point_ability(building_line)) - - # Resource abilities - if building_line.is_harvestable(): - abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(building_line)) - - if building_line.is_dropsite(): - abilities_set.append(AoCAbilitySubprocessor.drop_site_ability(building_line)) - - ability = AoCAbilitySubprocessor.provide_contingent_ability(building_line) - if ability: - abilities_set.append(ability) - - # Trade abilities - if building_line.is_trade_post(): - abilities_set.append(SWGBCCAbilitySubprocessor.trade_post_ability(building_line)) - - if building_line.get_id() == 84: - # Spaceport trading - abilities_set.extend( - SWGBCCAbilitySubprocessor.exchange_resources_ability(building_line)) - - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - raw_api_object.add_raw_member("modifiers", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - raw_api_object.add_raw_member("variants", [], "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Misc (Objects that are not used by the unit line itself, but use its values) - # ======================================================================= - if building_line.is_creatable(): - SWGBCCAuxiliarySubprocessor.get_creatable_game_entity(building_line) - - @staticmethod - def ambient_group_to_game_entity(ambient_group: GenieAmbientGroup) -> None: - """ - Creates raw API objects for an ambient group. - - :param ambient_group: Unit line that gets converted to a game entity. - :type ambient_group: ..dataformat.converter_object.ConverterObjectGroup - """ - ambient_unit = ambient_group.get_head_unit() - ambient_id = ambient_group.get_head_unit_id() - - dataset = ambient_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - class_lookup_dict = internal_name_lookups.get_class_lookups(dataset.game_version) - - # Start with the generic GameEntity - game_entity_name = name_lookup_dict[ambient_id][0] - obj_location = f"data/game_entity/generic/{name_lookup_dict[ambient_id][1]}/" - raw_api_object = RawAPIObject(game_entity_name, game_entity_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(name_lookup_dict[ambient_id][1]) - ambient_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Game Entity Types - # ======================================================================= - # we give an ambient the types - # - util.game_entity_type.types.Ambient - # ======================================================================= - # Create or use existing auxiliary types - types_set = [] - - type_obj = dataset.pregen_nyan_objects["util.game_entity_type.types.Ambient"].get_nyan_object( - ) - types_set.append(type_obj) - - unit_class = ambient_unit["unit_class"].value - class_name = class_lookup_dict[unit_class] - class_obj_name = f"util.game_entity_type.types.{class_name}" - type_obj = dataset.pregen_nyan_objects[class_obj_name].get_nyan_object() - types_set.append(type_obj) - - raw_api_object.add_raw_member("types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - - interaction_mode = ambient_unit["interaction_mode"].value - - if interaction_mode >= 0: - abilities_set.append(AoCAbilitySubprocessor.death_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.collision_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.idle_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.live_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.named_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.resistance_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.visibility_ability(ambient_group)) - - if interaction_mode >= 2: - abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(ambient_group)) - - if not ambient_group.is_passable(): - abilities_set.append(AoCAbilitySubprocessor.pathable_ability(ambient_group)) - - if ambient_group.is_harvestable(): - abilities_set.append(SWGBCCAbilitySubprocessor.harvestable_ability(ambient_group)) - - # ======================================================================= - # Abilities - # ======================================================================= - raw_api_object.add_raw_member("abilities", abilities_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers_set = [] - - raw_api_object.add_raw_member("modifiers", modifiers_set, - "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Variants - # ======================================================================= - variants_set = [] - - raw_api_object.add_raw_member("variants", variants_set, - "engine.util.game_entity.GameEntity") - - @staticmethod - def tech_group_to_tech(tech_group: GenieTechEffectBundleGroup) -> None: - """ - Creates raw API objects for a tech group. - - :param tech_group: Tech group that gets converted to a tech. - :type tech_group: ..dataformat.converter_object.ConverterObjectGroup - """ - tech_id = tech_group.get_id() - - # Skip Tech Level 0 tech - if tech_id == 0: - return - - dataset = tech_group.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - - # Start with the Tech object - tech_name = tech_lookup_dict[tech_id][0] - raw_api_object = RawAPIObject(tech_name, tech_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.tech.Tech") - - if isinstance(tech_group, UnitLineUpgrade): - unit_line = dataset.unit_lines[tech_group.get_line_id()] - head_unit_id = unit_line.get_head_unit_id() - obj_location = f"data/game_entity/generic/{name_lookup_dict[head_unit_id][1]}/" - - else: - obj_location = f"data/tech/generic/{tech_lookup_dict[tech_id][1]}/" - - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(tech_lookup_dict[tech_id][1]) - tech_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Types - # ======================================================================= - raw_api_object.add_raw_member("types", [], "engine.util.tech.Tech") - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{tech_name}.{tech_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{tech_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(tech_group, tech_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(tech_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.tech.Tech") - tech_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Description - # ======================================================================= - description_ref = f"{tech_name}.{tech_name}Description" - description_raw_api_object = RawAPIObject(description_ref, - f"{tech_name}Description", - dataset.nyan_api_objects) - description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - description_location = ForwardRef(tech_group, tech_name) - description_raw_api_object.set_location(description_location) - - description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - description_forward_ref = ForwardRef(tech_group, description_ref) - raw_api_object.add_raw_member("description", - description_forward_ref, - "engine.util.tech.Tech") - tech_group.add_raw_api_object(description_raw_api_object) - - # ======================================================================= - # Long description - # ======================================================================= - long_description_ref = f"{tech_name}.{tech_name}LongDescription" - long_description_raw_api_object = RawAPIObject(long_description_ref, - f"{tech_name}LongDescription", - dataset.nyan_api_objects) - long_description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - long_description_location = ForwardRef(tech_group, tech_name) - long_description_raw_api_object.set_location(long_description_location) - - long_description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - long_description_forward_ref = ForwardRef(tech_group, long_description_ref) - raw_api_object.add_raw_member("long_description", - long_description_forward_ref, - "engine.util.tech.Tech") - tech_group.add_raw_api_object(long_description_raw_api_object) - - # ======================================================================= - # Updates - # ======================================================================= - patches = [] - patches.extend(SWGBCCTechSubprocessor.get_patches(tech_group)) - raw_api_object.add_raw_member("updates", patches, "engine.util.tech.Tech") - - # ======================================================================= - # Misc (Objects that are not used by the tech group itself, but use its values) - # ======================================================================= - if tech_group.is_researchable(): - SWGBCCAuxiliarySubprocessor.get_researchable_tech(tech_group) - - # TODO: Implement civ line techs - - @staticmethod - def civ_group_to_civ(civ_group: GenieCivilizationGroup) -> None: - """ - Creates raw API objects for a civ group. - - :param civ_group: Terrain group that gets converted to a tech. - :type civ_group: ..dataformat.converter_object.ConverterObjectGroup - """ - civ_id = civ_group.get_id() - - dataset = civ_group.data - - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - - # Start with the Tech object - tech_name = civ_lookup_dict[civ_id][0] - raw_api_object = RawAPIObject(tech_name, tech_name, - dataset.nyan_api_objects) - raw_api_object.add_raw_parent("engine.util.setup.PlayerSetup") - - obj_location = f"data/civ/{civ_lookup_dict[civ_id][1]}/" - - raw_api_object.set_location(obj_location) - raw_api_object.set_filename(civ_lookup_dict[civ_id][1]) - civ_group.add_raw_api_object(raw_api_object) - - # ======================================================================= - # Name - # ======================================================================= - name_ref = f"{tech_name}.{tech_name}Name" - name_raw_api_object = RawAPIObject(name_ref, - f"{tech_name}Name", - dataset.nyan_api_objects) - name_raw_api_object.add_raw_parent("engine.util.language.translated.type.TranslatedString") - name_location = ForwardRef(civ_group, tech_name) - name_raw_api_object.set_location(name_location) - - name_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedString") - - name_forward_ref = ForwardRef(civ_group, name_ref) - raw_api_object.add_raw_member("name", name_forward_ref, "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(name_raw_api_object) - - # ======================================================================= - # Description - # ======================================================================= - description_ref = f"{tech_name}.{tech_name}Description" - description_raw_api_object = RawAPIObject(description_ref, - f"{tech_name}Description", - dataset.nyan_api_objects) - description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - description_location = ForwardRef(civ_group, tech_name) - description_raw_api_object.set_location(description_location) - - description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - description_forward_ref = ForwardRef(civ_group, description_ref) - raw_api_object.add_raw_member("description", - description_forward_ref, - "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(description_raw_api_object) - - # ======================================================================= - # Long description - # ======================================================================= - long_description_ref = f"{tech_name}.{tech_name}LongDescription" - long_description_raw_api_object = RawAPIObject(long_description_ref, - f"{tech_name}LongDescription", - dataset.nyan_api_objects) - long_description_raw_api_object.add_raw_parent( - "engine.util.language.translated.type.TranslatedMarkupFile") - long_description_location = ForwardRef(civ_group, tech_name) - long_description_raw_api_object.set_location(long_description_location) - - long_description_raw_api_object.add_raw_member("translations", - [], - "engine.util.language.translated.type.TranslatedMarkupFile") - - long_description_forward_ref = ForwardRef(civ_group, long_description_ref) - raw_api_object.add_raw_member("long_description", - long_description_forward_ref, - "engine.util.setup.PlayerSetup") - civ_group.add_raw_api_object(long_description_raw_api_object) - - # ======================================================================= - # TODO: Leader names - # ======================================================================= - raw_api_object.add_raw_member("leader_names", - [], - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Modifiers - # ======================================================================= - modifiers = SWGBCCCivSubprocessor.get_modifiers(civ_group) - raw_api_object.add_raw_member("modifiers", - modifiers, - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Starting resources - # ======================================================================= - resource_amounts = SWGBCCCivSubprocessor.get_starting_resources(civ_group) - raw_api_object.add_raw_member("starting_resources", - resource_amounts, - "engine.util.setup.PlayerSetup") - - # ======================================================================= - # Game setup - # ======================================================================= - game_setup = SWGBCCCivSubprocessor.get_civ_setup(civ_group) - raw_api_object.add_raw_member("game_setup", - game_setup, - "engine.util.setup.PlayerSetup") - - @staticmethod - def projectiles_from_line(line): - """ - Creates Projectile(GameEntity) raw API objects for a unit/building line. - - :param line: Line for which the projectiles are extracted. - :type line: ..dataformat.converter_object.ConverterObjectGroup - """ - current_unit = line.get_head_unit() - 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] - game_entity_filename = name_lookup_dict[current_unit_id][1] - - projectiles_location = f"data/game_entity/generic/{game_entity_filename}/projectiles/" - - projectile_indices = [] - projectile_primary = current_unit["projectile_id0"].value - if projectile_primary > -1: - projectile_indices.append(0) - - projectile_secondary = current_unit["projectile_id1"].value - if projectile_secondary > -1: - projectile_indices.append(1) - - for projectile_num in projectile_indices: - obj_ref = f"{game_entity_name}.ShootProjectile.Projectile{projectile_num}" - obj_name = f"Projectile{str(projectile_num)}" - proj_raw_api_object = RawAPIObject(obj_ref, obj_name, dataset.nyan_api_objects) - proj_raw_api_object.add_raw_parent("engine.util.game_entity.GameEntity") - proj_raw_api_object.set_location(projectiles_location) - proj_raw_api_object.set_filename(f"{game_entity_filename}_projectiles") - - # ======================================================================= - # Types - # ======================================================================= - types_set = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Projectile"].get_nyan_object()] - proj_raw_api_object.add_raw_member( - "types", types_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Abilities - # ======================================================================= - abilities_set = [] - abilities_set.append(AoCAbilitySubprocessor.projectile_ability( - line, position=projectile_num)) - abilities_set.append(AoCAbilitySubprocessor.move_projectile_ability( - line, position=projectile_num)) - abilities_set.append(AoCAbilitySubprocessor.apply_discrete_effect_ability( - line, 7, False, projectile_num)) - # TODO: Death, Despawn - proj_raw_api_object.add_raw_member( - "abilities", abilities_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # TODO: Modifiers - # ======================================================================= - modifiers_set = [] - - # modifiers_set.append(AoCModifierSubprocessor.flyover_effect_modifier(line)) - # modifiers_set.extend(AoCModifierSubprocessor.elevation_attack_modifiers(line)) - - proj_raw_api_object.add_raw_member( - "modifiers", modifiers_set, "engine.util.game_entity.GameEntity") - - # ======================================================================= - # Variants - # ======================================================================= - variants_set = [] - proj_raw_api_object.add_raw_member( - "variants", variants_set, "engine.util.game_entity.GameEntity") - - line.add_raw_api_object(proj_raw_api_object) - - # TODO: Implement diffing of civ lines + ambient_group_to_game_entity = staticmethod(ambient_group_to_game_entity) + building_line_to_game_entity = staticmethod(building_line_to_game_entity) + civ_group_to_civ = staticmethod(civ_group_to_civ) + projectiles_from_line = staticmethod(projectiles_from_line) + tech_group_to_tech = staticmethod(tech_group_to_tech) + unit_line_to_game_entity = staticmethod(unit_line_to_game_entity) From f5ddc6a7b94b087e01da44c44f0781cb8e1ff62c Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 17:48:53 +0200 Subject: [PATCH 149/163] convert: Refactor SWGBCCPregenSubprocessor into separate files. --- .../processor/conversion/aoc/pregen/effect.py | 15 +- .../conversion/aoc/pregen/exchange.py | 23 +- .../conversion/aoc/pregen/resource.py | 46 +- .../conversion/swgbcc/CMakeLists.txt | 1 + .../conversion/swgbcc/pregen/CMakeLists.txt | 6 + .../conversion/swgbcc/pregen/__init__.py | 6 + .../conversion/swgbcc/pregen/effect.py | 126 ++++ .../conversion/swgbcc/pregen/exchange.py | 220 ++++++ .../conversion/swgbcc/pregen/resource.py | 166 +++++ .../conversion/swgbcc/pregen_subprocessor.py | 669 +----------------- 10 files changed, 578 insertions(+), 700 deletions(-) create mode 100644 openage/convert/processor/conversion/swgbcc/pregen/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/pregen/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/pregen/effect.py create mode 100644 openage/convert/processor/conversion/swgbcc/pregen/exchange.py create mode 100644 openage/convert/processor/conversion/swgbcc/pregen/resource.py diff --git a/openage/convert/processor/conversion/aoc/pregen/effect.py b/openage/convert/processor/conversion/aoc/pregen/effect.py index b5abae1695..dba56ff038 100644 --- a/openage/convert/processor/conversion/aoc/pregen/effect.py +++ b/openage/convert/processor/conversion/aoc/pregen/effect.py @@ -7,11 +7,12 @@ import typing from ......nyan.nyan_structs import MemberSpecialValue -from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....entity_object.conversion.converter_object import RawAPIObject from .....service.conversion import internal_name_lookups from .....value_object.conversion.forward_ref import ForwardRef if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer ATTRIBUTE_CHANGE_PARENT = "engine.util.attribute_change_type.AttributeChangeType" @@ -45,9 +46,9 @@ def generate_effect_types( :param full_data_set: Storage for all converted objects and metadata. :param pregen_converter_group: Stores all pregenerated nyan objects. """ - _generate_attribute_change_types(full_data_set, pregen_converter_group) - _generate_construct_types(full_data_set, pregen_converter_group) - _generate_convert_types(full_data_set, pregen_converter_group) + generate_attribute_change_types(full_data_set, pregen_converter_group) + generate_construct_types(full_data_set, pregen_converter_group) + generate_convert_types(full_data_set, pregen_converter_group) def generate_misc_effect_objects( @@ -67,7 +68,7 @@ def generate_misc_effect_objects( _generate_repair_property(full_data_set, pregen_converter_group) -def _generate_attribute_change_types( +def generate_attribute_change_types( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -156,7 +157,7 @@ def _generate_attribute_change_types( pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) -def _generate_construct_types( +def generate_construct_types( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -189,7 +190,7 @@ def _generate_construct_types( pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) -def _generate_convert_types( +def generate_convert_types( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: diff --git a/openage/convert/processor/conversion/aoc/pregen/exchange.py b/openage/convert/processor/conversion/aoc/pregen/exchange.py index 07076aabbb..d5894dc4e7 100644 --- a/openage/convert/processor/conversion/aoc/pregen/exchange.py +++ b/openage/convert/processor/conversion/aoc/pregen/exchange.py @@ -6,10 +6,11 @@ from __future__ import annotations import typing -from .....entity_object.conversion.converter_object import RawAPIObject, ConverterObjectGroup +from .....entity_object.conversion.converter_object import RawAPIObject from .....value_object.conversion.forward_ref import ForwardRef if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer BUY_MODE_PARENT = "engine.util.exchange_mode.type.Buy" @@ -30,13 +31,13 @@ def generate_exchange_objects( :param full_data_set: Storage for all converted objects and metadata. :param pregen_converter_group: Stores all pregenerated nyan objects. """ - _generate_exchange_modes(full_data_set, pregen_converter_group) - _generate_exchange_rates(full_data_set, pregen_converter_group) - _generate_price_pools(full_data_set, pregen_converter_group) - _generate_price_modes(full_data_set, pregen_converter_group) + generate_exchange_modes(full_data_set, pregen_converter_group) + generate_exchange_rates(full_data_set, pregen_converter_group) + generate_price_pools(full_data_set, pregen_converter_group) + generate_price_modes(full_data_set, pregen_converter_group) -def _generate_exchange_modes( +def generate_exchange_modes( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -88,7 +89,7 @@ def _generate_exchange_modes( pregen_nyan_objects.update({exchange_mode_ref_in_modpack: exchange_mode_raw_api_object}) -def _generate_exchange_rates( +def generate_exchange_rates( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -219,7 +220,7 @@ def _generate_exchange_rates( pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) -def _generate_price_pools( +def generate_price_pools( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -275,7 +276,7 @@ def _generate_price_pools( pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) -def _generate_price_modes( +def generate_price_modes( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -299,7 +300,7 @@ def _generate_price_modes( price_mode_raw_api_object.set_filename("market_trading") price_mode_raw_api_object.add_raw_parent(PRICE_MODE_PARENT) - # Min price + # Change value price_mode_raw_api_object.add_raw_member("change_value", 0.03, PRICE_MODE_PARENT) @@ -328,7 +329,7 @@ def _generate_price_modes( price_mode_raw_api_object.set_filename("market_trading") price_mode_raw_api_object.add_raw_parent(PRICE_MODE_PARENT) - # Min price + # Change value price_mode_raw_api_object.add_raw_member("change_value", -0.03, PRICE_MODE_PARENT) diff --git a/openage/convert/processor/conversion/aoc/pregen/resource.py b/openage/convert/processor/conversion/aoc/pregen/resource.py index 88253940b8..ff881b88cf 100644 --- a/openage/convert/processor/conversion/aoc/pregen/resource.py +++ b/openage/convert/processor/conversion/aoc/pregen/resource.py @@ -15,6 +15,7 @@ RESOURCE_PARENT = "engine.util.resource.Resource" RESOURCE_CONTINGENT_PARENT = "engine.util.resource.ResourceContingent" +NAME_VALUE_PARENT = "engine.util.language.translated.type.TranslatedString" RESOURCES_LOCATION = "data/util/resource/" @@ -28,14 +29,14 @@ def generate_resources( :param full_data_set: Storage for all converted objects and metadata. :param pregen_converter_group: Stores all pregenerated nyan objects. """ - _generate_food_resource(full_data_set, pregen_converter_group) - _generate_wood_resource(full_data_set, pregen_converter_group) - _generate_stone_resource(full_data_set, pregen_converter_group) - _generate_gold_resource(full_data_set, pregen_converter_group) - _generate_population_resource(full_data_set, pregen_converter_group) + generate_food_resource(full_data_set, pregen_converter_group) + generate_wood_resource(full_data_set, pregen_converter_group) + generate_stone_resource(full_data_set, pregen_converter_group) + generate_gold_resource(full_data_set, pregen_converter_group) + generate_population_resource(full_data_set, pregen_converter_group) -def _generate_food_resource( +def generate_food_resource( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -62,13 +63,12 @@ def _generate_food_resource( MemberSpecialValue.NYAN_INF, RESOURCE_PARENT) - name_value_parent = "engine.util.language.translated.type.TranslatedString" food_name_ref_in_modpack = "util.attribute.types.Food.FoodName" food_name_value = RawAPIObject(food_name_ref_in_modpack, "FoodName", api_objects, RESOURCES_LOCATION) food_name_value.set_filename("types") - food_name_value.add_raw_parent(name_value_parent) - food_name_value.add_raw_member("translations", [], name_value_parent) + food_name_value.add_raw_parent(NAME_VALUE_PARENT) + food_name_value.add_raw_member("translations", [], NAME_VALUE_PARENT) name_forward_ref = ForwardRef(pregen_converter_group, food_name_ref_in_modpack) @@ -80,7 +80,7 @@ def _generate_food_resource( pregen_nyan_objects.update({food_name_ref_in_modpack: food_name_value}) -def _generate_wood_resource( +def generate_wood_resource( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -107,13 +107,12 @@ def _generate_wood_resource( MemberSpecialValue.NYAN_INF, RESOURCE_PARENT) - name_value_parent = "engine.util.language.translated.type.TranslatedString" wood_name_ref_in_modpack = "util.attribute.types.Wood.WoodName" wood_name_value = RawAPIObject(wood_name_ref_in_modpack, "WoodName", api_objects, RESOURCES_LOCATION) wood_name_value.set_filename("types") - wood_name_value.add_raw_parent(name_value_parent) - wood_name_value.add_raw_member("translations", [], name_value_parent) + wood_name_value.add_raw_parent(NAME_VALUE_PARENT) + wood_name_value.add_raw_member("translations", [], NAME_VALUE_PARENT) name_forward_ref = ForwardRef(pregen_converter_group, wood_name_ref_in_modpack) @@ -125,7 +124,7 @@ def _generate_wood_resource( pregen_nyan_objects.update({wood_name_ref_in_modpack: wood_name_value}) -def _generate_stone_resource( +def generate_stone_resource( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -152,13 +151,12 @@ def _generate_stone_resource( MemberSpecialValue.NYAN_INF, RESOURCE_PARENT) - name_value_parent = "engine.util.language.translated.type.TranslatedString" stone_name_ref_in_modpack = "util.attribute.types.Stone.StoneName" stone_name_value = RawAPIObject(stone_name_ref_in_modpack, "StoneName", api_objects, RESOURCES_LOCATION) stone_name_value.set_filename("types") - stone_name_value.add_raw_parent(name_value_parent) - stone_name_value.add_raw_member("translations", [], name_value_parent) + stone_name_value.add_raw_parent(NAME_VALUE_PARENT) + stone_name_value.add_raw_member("translations", [], NAME_VALUE_PARENT) name_forward_ref = ForwardRef(pregen_converter_group, stone_name_ref_in_modpack) @@ -170,7 +168,7 @@ def _generate_stone_resource( pregen_nyan_objects.update({stone_name_ref_in_modpack: stone_name_value}) -def _generate_gold_resource( +def generate_gold_resource( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -197,13 +195,12 @@ def _generate_gold_resource( MemberSpecialValue.NYAN_INF, RESOURCE_PARENT) - name_value_parent = "engine.util.language.translated.type.TranslatedString" gold_name_ref_in_modpack = "util.attribute.types.Gold.GoldName" gold_name_value = RawAPIObject(gold_name_ref_in_modpack, "GoldName", api_objects, RESOURCES_LOCATION) gold_name_value.set_filename("types") - gold_name_value.add_raw_parent(name_value_parent) - gold_name_value.add_raw_member("translations", [], name_value_parent) + gold_name_value.add_raw_parent(NAME_VALUE_PARENT) + gold_name_value.add_raw_member("translations", [], NAME_VALUE_PARENT) name_forward_ref = ForwardRef(pregen_converter_group, gold_name_ref_in_modpack) @@ -215,7 +212,7 @@ def _generate_gold_resource( pregen_nyan_objects.update({gold_name_ref_in_modpack: gold_name_value}) -def _generate_population_resource( +def generate_population_resource( full_data_set: GenieObjectContainer, pregen_converter_group: ConverterObjectGroup ) -> None: @@ -238,13 +235,12 @@ def _generate_population_resource( pregen_converter_group.add_raw_api_object(pop_raw_api_object) pregen_nyan_objects.update({pop_ref_in_modpack: pop_raw_api_object}) - name_value_parent = "engine.util.language.translated.type.TranslatedString" pop_name_ref_in_modpack = "util.attribute.types.PopulationSpace.PopulationSpaceName" pop_name_value = RawAPIObject(pop_name_ref_in_modpack, "PopulationSpaceName", api_objects, RESOURCES_LOCATION) pop_name_value.set_filename("types") - pop_name_value.add_raw_parent(name_value_parent) - pop_name_value.add_raw_member("translations", [], name_value_parent) + pop_name_value.add_raw_parent(NAME_VALUE_PARENT) + pop_name_value.add_raw_member("translations", [], NAME_VALUE_PARENT) name_forward_ref = ForwardRef(pregen_converter_group, pop_name_ref_in_modpack) diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index 812479d69d..494e4d17d8 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -16,3 +16,4 @@ add_subdirectory(ability) add_subdirectory(auxiliary) add_subdirectory(civ) add_subdirectory(nyan) +add_subdirectory(pregen) diff --git a/openage/convert/processor/conversion/swgbcc/pregen/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/pregen/CMakeLists.txt new file mode 100644 index 0000000000..9ab5382bef --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/pregen/CMakeLists.txt @@ -0,0 +1,6 @@ +add_py_modules( + __init__.py + effect.py + exchange.py + resource.py +) diff --git a/openage/convert/processor/conversion/swgbcc/pregen/__init__.py b/openage/convert/processor/conversion/swgbcc/pregen/__init__.py new file mode 100644 index 0000000000..07febc40fc --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/pregen/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates nyan objects for things that are hardcoded into the Genie Engine, +but configurable in openage, e.g. HP. +""" diff --git a/openage/convert/processor/conversion/swgbcc/pregen/effect.py b/openage/convert/processor/conversion/swgbcc/pregen/effect.py new file mode 100644 index 0000000000..1a75d48bc4 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/pregen/effect.py @@ -0,0 +1,126 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create effect objects for SWGB. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....entity_object.conversion.swgbcc.genie_unit import SWGBUnitTransformGroup +from .....service.conversion import internal_name_lookups +from ...aoc.pregen.effect import ATTRIBUTE_CHANGE_PARENT, ATTRIBUTE_CHANGE_LOCATION +from ...aoc.pregen import effect as aoc_pregen_effect + + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def generate_effect_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate types for effects and resistances. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + _generate_attribute_change_types(full_data_set, pregen_converter_group) + aoc_pregen_effect.generate_construct_types(full_data_set, pregen_converter_group) + aoc_pregen_effect.generate_convert_types(full_data_set, pregen_converter_group) + + +def _generate_attribute_change_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the attribute change types for effects and resistances from + the armor class lookups. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + name_lookup_dict = internal_name_lookups.get_entity_lookups(full_data_set.game_version) + armor_lookup_dict = internal_name_lookups.get_armor_class_lookups( + full_data_set.game_version) + + # ======================================================================= + # Armor types + # ======================================================================= + for type_name in armor_lookup_dict.values(): + type_ref_in_modpack = f"util.attribute_change_type.types.{type_name}" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + type_name, api_objects, + ATTRIBUTE_CHANGE_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(ATTRIBUTE_CHANGE_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + # ======================================================================= + # Heal + # ======================================================================= + type_ref_in_modpack = "util.attribute_change_type.types.Heal" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + "Heal", api_objects, + ATTRIBUTE_CHANGE_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(ATTRIBUTE_CHANGE_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + # ======================================================================= + # Repair (one for each repairable entity) + # ======================================================================= + repairable_lines = [] + repairable_lines.extend(full_data_set.building_lines.values()) + for unit_line in full_data_set.unit_lines.values(): + if unit_line.is_repairable(): + repairable_lines.append(unit_line) + + for repairable_line in repairable_lines: + if isinstance(repairable_line, SWGBUnitTransformGroup): + game_entity_name = name_lookup_dict[repairable_line.get_transform_unit_id()][0] + + else: + game_entity_name = name_lookup_dict[repairable_line.get_head_unit_id()][0] + + type_ref_in_modpack = f"util.attribute_change_type.types.{game_entity_name}Repair" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + f"{game_entity_name}Repair", + api_objects, + ATTRIBUTE_CHANGE_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(ATTRIBUTE_CHANGE_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + + # ======================================================================= + # Construct (two for each constructable entity) + # ======================================================================= + constructable_lines = [] + constructable_lines.extend(full_data_set.building_lines.values()) + + for constructable_line in constructable_lines: + game_entity_name = name_lookup_dict[constructable_line.get_head_unit_id()][0] + + type_ref_in_modpack = f"util.attribute_change_type.types.{game_entity_name}Construct" + type_raw_api_object = RawAPIObject(type_ref_in_modpack, + f"{game_entity_name}Construct", + api_objects, + ATTRIBUTE_CHANGE_LOCATION) + type_raw_api_object.set_filename("types") + type_raw_api_object.add_raw_parent(ATTRIBUTE_CHANGE_PARENT) + + pregen_converter_group.add_raw_api_object(type_raw_api_object) + pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) diff --git a/openage/convert/processor/conversion/swgbcc/pregen/exchange.py b/openage/convert/processor/conversion/swgbcc/pregen/exchange.py new file mode 100644 index 0000000000..f9df45bb9f --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/pregen/exchange.py @@ -0,0 +1,220 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create exchange objects for trading in SWGB. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.converter_object import RawAPIObject +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.pregen.exchange import EXCHANGE_OBJECTS_LOCATION, EXCHANGE_RATE_PARENT, \ + PRICE_POOL_PARENT +from ...aoc.pregen import exchange as aoc_pregen_exchange + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def generate_exchange_objects( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate objects for market trading (ExchangeResources). + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + aoc_pregen_exchange.generate_exchange_modes(full_data_set, pregen_converter_group) + _generate_exchange_rates(full_data_set, pregen_converter_group) + _generate_price_pools(full_data_set, pregen_converter_group) + aoc_pregen_exchange.generate_price_modes(full_data_set, pregen_converter_group) + + +def _generate_exchange_rates( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate exchange rates for trading in SWGB. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Exchange rate Food + # ======================================================================= + exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketFoodExchangeRate" + exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, + "MarketFoodExchangeRate", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_rate_raw_api_object.set_filename("market_trading") + exchange_rate_raw_api_object.add_raw_parent(EXCHANGE_RATE_PARENT) + + # Base price + exchange_rate_raw_api_object.add_raw_member("base_price", + 1.0, + EXCHANGE_RATE_PARENT) + + # Price adjust methods + pa_buy_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketBuyPriceMode") + pa_sell_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketSellPriceMode") + price_adjust = { + api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, + api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref + } + exchange_rate_raw_api_object.add_raw_member("price_adjust", + price_adjust, + EXCHANGE_RATE_PARENT) + + # Price pool + pool_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketFoodPricePool") + exchange_rate_raw_api_object.add_raw_member("price_pool", + pool_forward_ref, + EXCHANGE_RATE_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) + pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) + + # ======================================================================= + # Exchange rate Carbon + # ======================================================================= + exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketCarbonExchangeRate" + exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, + "MarketCarbonExchangeRate", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_rate_raw_api_object.set_filename("market_trading") + exchange_rate_raw_api_object.add_raw_parent(EXCHANGE_RATE_PARENT) + + # Base price + exchange_rate_raw_api_object.add_raw_member("base_price", + 1.0, + EXCHANGE_RATE_PARENT) + + # Price adjust methods + pa_buy_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketBuyPriceMode") + pa_sell_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketSellPriceMode") + price_adjust = { + api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, + api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref + } + exchange_rate_raw_api_object.add_raw_member("price_adjust", + price_adjust, + EXCHANGE_RATE_PARENT) + + # Price pool + pool_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketCarbonPricePool") + exchange_rate_raw_api_object.add_raw_member("price_pool", + pool_forward_ref, + EXCHANGE_RATE_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) + pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) + + # ======================================================================= + # Exchange rate Ore + # ======================================================================= + exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketOreExchangeRate" + exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, + "MarketOreExchangeRate", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_rate_raw_api_object.set_filename("market_trading") + exchange_rate_raw_api_object.add_raw_parent(EXCHANGE_RATE_PARENT) + + # Base price + exchange_rate_raw_api_object.add_raw_member("base_price", + 1.3, + EXCHANGE_RATE_PARENT) + + # Price adjust methods + pa_buy_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketBuyPriceMode") + pa_sell_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketSellPriceMode") + price_adjust = { + api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, + api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref + } + exchange_rate_raw_api_object.add_raw_member("price_adjust", + price_adjust, + EXCHANGE_RATE_PARENT) + + # Price pool + pool_forward_ref = ForwardRef(pregen_converter_group, + "util.resource.market_trading.MarketOrePricePool") + exchange_rate_raw_api_object.add_raw_member("price_pool", + pool_forward_ref, + EXCHANGE_RATE_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) + pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) + + +def _generate_price_pools( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate price pools for trading in SWGB. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + # ======================================================================= + # Market Food price pool + # ======================================================================= + exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketFoodPricePool" + exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, + "MarketFoodPricePool", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_pool_raw_api_object.set_filename("market_trading") + exchange_pool_raw_api_object.add_raw_parent(PRICE_POOL_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) + pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) + + # ======================================================================= + # Market Carbon price pool + # ======================================================================= + exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketCarbonPricePool" + exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, + "MarketCarbonPricePool", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_pool_raw_api_object.set_filename("market_trading") + exchange_pool_raw_api_object.add_raw_parent(PRICE_POOL_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) + pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) + + # ======================================================================= + # Market Ore price pool + # ======================================================================= + exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketOrePricePool" + exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, + "MarketOrePricePool", + api_objects, + EXCHANGE_OBJECTS_LOCATION) + exchange_pool_raw_api_object.set_filename("market_trading") + exchange_pool_raw_api_object.add_raw_parent(PRICE_POOL_PARENT) + + pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) + pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) diff --git a/openage/convert/processor/conversion/swgbcc/pregen/resource.py b/openage/convert/processor/conversion/swgbcc/pregen/resource.py new file mode 100644 index 0000000000..7d537cf1b1 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/pregen/resource.py @@ -0,0 +1,166 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create resources and resource types for SWGB. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberSpecialValue +from .....entity_object.conversion.converter_object import RawAPIObject +from .....value_object.conversion.forward_ref import ForwardRef +from ...aoc.pregen import resource as aoc_pregen_resource +from ...aoc.pregen.resource import RESOURCE_PARENT, RESOURCES_LOCATION, NAME_VALUE_PARENT + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def generate_resources( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate Resource objects. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + aoc_pregen_resource.generate_food_resource(full_data_set, pregen_converter_group) + generate_carbon_resource(full_data_set, pregen_converter_group) + generate_ore_resource(full_data_set, pregen_converter_group) + generate_nova_resource(full_data_set, pregen_converter_group) + aoc_pregen_resource.generate_population_resource(full_data_set, pregen_converter_group) + + +def generate_carbon_resource( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the carbon resource type. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + carbon_ref_in_modpack = "util.resource.types.Carbon" + carbon_raw_api_object = RawAPIObject(carbon_ref_in_modpack, + "Carbon", api_objects, + RESOURCES_LOCATION) + carbon_raw_api_object.set_filename("types") + carbon_raw_api_object.add_raw_parent(RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(carbon_raw_api_object) + pregen_nyan_objects.update({carbon_ref_in_modpack: carbon_raw_api_object}) + + carbon_raw_api_object.add_raw_member("max_storage", + MemberSpecialValue.NYAN_INF, + RESOURCE_PARENT) + + carbon_name_ref_in_modpack = "util.attribute.types.Carbon.CarbonName" + carbon_name_value = RawAPIObject(carbon_name_ref_in_modpack, "CarbonName", + api_objects, RESOURCES_LOCATION) + carbon_name_value.set_filename("types") + carbon_name_value.add_raw_parent(NAME_VALUE_PARENT) + carbon_name_value.add_raw_member("translations", [], NAME_VALUE_PARENT) + + name_forward_ref = ForwardRef(pregen_converter_group, + carbon_name_ref_in_modpack) + carbon_raw_api_object.add_raw_member("name", + name_forward_ref, + RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(carbon_name_value) + pregen_nyan_objects.update({carbon_name_ref_in_modpack: carbon_name_value}) + + +def generate_ore_resource( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the ore resource type. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + ore_ref_in_modpack = "util.resource.types.Ore" + ore_raw_api_object = RawAPIObject(ore_ref_in_modpack, + "Ore", api_objects, + RESOURCES_LOCATION) + ore_raw_api_object.set_filename("types") + ore_raw_api_object.add_raw_parent(RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(ore_raw_api_object) + pregen_nyan_objects.update({ore_ref_in_modpack: ore_raw_api_object}) + + ore_raw_api_object.add_raw_member("max_storage", + MemberSpecialValue.NYAN_INF, + RESOURCE_PARENT) + + ore_name_ref_in_modpack = "util.attribute.types.Ore.OreName" + ore_name_value = RawAPIObject(ore_name_ref_in_modpack, "OreName", + api_objects, RESOURCES_LOCATION) + ore_name_value.set_filename("types") + ore_name_value.add_raw_parent(NAME_VALUE_PARENT) + ore_name_value.add_raw_member("translations", [], NAME_VALUE_PARENT) + + name_forward_ref = ForwardRef(pregen_converter_group, + ore_name_ref_in_modpack) + ore_raw_api_object.add_raw_member("name", + name_forward_ref, + RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(ore_name_value) + pregen_nyan_objects.update({ore_name_ref_in_modpack: ore_name_value}) + + +def generate_nova_resource( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup +) -> None: + """ + Generate the nova resource type. + + :param full_data_set: Storage for all converted objects and metadata. + :param pregen_converter_group: Stores all pregenerated nyan objects. + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + nova_ref_in_modpack = "util.resource.types.Nova" + nova_raw_api_object = RawAPIObject(nova_ref_in_modpack, + "Nova", api_objects, + RESOURCES_LOCATION) + nova_raw_api_object.set_filename("types") + nova_raw_api_object.add_raw_parent(RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(nova_raw_api_object) + pregen_nyan_objects.update({nova_ref_in_modpack: nova_raw_api_object}) + + nova_raw_api_object.add_raw_member("max_storage", + MemberSpecialValue.NYAN_INF, + RESOURCE_PARENT) + + nova_name_ref_in_modpack = "util.attribute.types.Nova.NovaName" + nova_name_value = RawAPIObject(nova_name_ref_in_modpack, "NovaName", + api_objects, RESOURCES_LOCATION) + nova_name_value.set_filename("types") + nova_name_value.add_raw_parent(NAME_VALUE_PARENT) + nova_name_value.add_raw_member("translations", [], NAME_VALUE_PARENT) + + name_forward_ref = ForwardRef(pregen_converter_group, + nova_name_ref_in_modpack) + nova_raw_api_object.add_raw_member("name", + name_forward_ref, + RESOURCE_PARENT) + + pregen_converter_group.add_raw_api_object(nova_name_value) + pregen_nyan_objects.update({nova_name_ref_in_modpack: nova_name_value}) diff --git a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py index 7425891ffa..ecb8454ad0 100644 --- a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py @@ -1,9 +1,4 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-statements -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates nyan objects for things that are hardcoded into the Genie Engine, @@ -12,14 +7,13 @@ from __future__ import annotations import typing -from .....nyan.nyan_structs import MemberSpecialValue -from ....entity_object.conversion.converter_object import ConverterObjectGroup, \ - RawAPIObject -from ....entity_object.conversion.swgbcc.genie_unit import SWGBUnitTransformGroup -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef +from ....entity_object.conversion.converter_object import ConverterObjectGroup from ..aoc.pregen_processor import AoCPregenSubprocessor +from .pregen.effect import generate_effect_types +from .pregen.exchange import generate_exchange_objects +from .pregen.resource import generate_resources + if typing.TYPE_CHECKING: from openage.convert.entity_object.conversion.aoc.genie_object_container import GenieObjectContainer @@ -42,15 +36,15 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: AoCPregenSubprocessor.generate_diplomatic_stances(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_team_property(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_entity_types(full_data_set, pregen_converter_group) - cls.generate_effect_types(full_data_set, pregen_converter_group) - cls.generate_exchange_objects(full_data_set, pregen_converter_group) + generate_effect_types(full_data_set, pregen_converter_group) + generate_exchange_objects(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_formation_types(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_language_objects(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_misc_effect_objects(full_data_set, pregen_converter_group) # cls._generate_modifiers(gamedata, pregen_converter_group) ?? # cls._generate_terrain_types(gamedata, pregen_converter_group) TODO: Create terrain types AoCPregenSubprocessor.generate_path_types(full_data_set, pregen_converter_group) - cls.generate_resources(full_data_set, pregen_converter_group) + generate_resources(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_death_condition(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_garrison_empty_condition(full_data_set, pregen_converter_group) @@ -67,645 +61,6 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: raise RuntimeError(f"{repr(pregen_object)}: Pregenerated object is not ready " "for export. Member or object not initialized.") - @staticmethod - def generate_effect_types( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate types for effects and resistances. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - name_lookup_dict = internal_name_lookups.get_entity_lookups(full_data_set.game_version) - armor_lookup_dict = internal_name_lookups.get_armor_class_lookups( - full_data_set.game_version) - - # ======================================================================= - # Armor types - # ======================================================================= - type_parent = "engine.util.attribute_change_type.AttributeChangeType" - types_location = "data/util/attribute_change_type/" - - for type_name in armor_lookup_dict.values(): - type_ref_in_modpack = f"util.attribute_change_type.types.{type_name}" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - type_name, api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # Heal - # ======================================================================= - type_ref_in_modpack = "util.attribute_change_type.types.Heal" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - "Heal", api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # Repair (one for each repairable entity) - # ======================================================================= - repairable_lines = [] - repairable_lines.extend(full_data_set.building_lines.values()) - for unit_line in full_data_set.unit_lines.values(): - if unit_line.is_repairable(): - repairable_lines.append(unit_line) - - for repairable_line in repairable_lines: - if isinstance(repairable_line, SWGBUnitTransformGroup): - game_entity_name = name_lookup_dict[repairable_line.get_transform_unit_id()][0] - - else: - game_entity_name = name_lookup_dict[repairable_line.get_head_unit_id()][0] - - type_ref_in_modpack = f"util.attribute_change_type.types.{game_entity_name}Repair" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - f"{game_entity_name}Repair", - api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # Construct (two for each constructable entity) - # ======================================================================= - constructable_lines = [] - constructable_lines.extend(full_data_set.building_lines.values()) - - for constructable_line in constructable_lines: - game_entity_name = name_lookup_dict[constructable_line.get_head_unit_id()][0] - - type_ref_in_modpack = f"util.attribute_change_type.types.{game_entity_name}Construct" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - f"{game_entity_name}Construct", - api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - type_parent = "engine.util.progress_type.type.Construct" - types_location = "data/util/construct_type/" - - for constructable_line in constructable_lines: - game_entity_name = name_lookup_dict[constructable_line.get_head_unit_id()][0] - - type_ref_in_modpack = f"util.construct_type.types.{game_entity_name}Construct" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - f"{game_entity_name}Construct", - api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # ConvertType: UnitConvert - # ======================================================================= - type_parent = "engine.util.convert_type.ConvertType" - types_location = "data/util/convert_type/" - - type_ref_in_modpack = "util.convert_type.types.UnitConvert" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - "UnitConvert", api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - # ======================================================================= - # ConvertType: BuildingConvert - # ======================================================================= - type_parent = "engine.util.convert_type.ConvertType" - types_location = "data/util/convert_type/" - - type_ref_in_modpack = "util.convert_type.types.BuildingConvert" - type_raw_api_object = RawAPIObject(type_ref_in_modpack, - "BuildingConvert", api_objects, - types_location) - type_raw_api_object.set_filename("types") - type_raw_api_object.add_raw_parent(type_parent) - - pregen_converter_group.add_raw_api_object(type_raw_api_object) - pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) - - @staticmethod - def generate_exchange_objects( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate objects for market trading (ExchangeResources). - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - # ======================================================================= - # Exchange mode Buy - # ======================================================================= - exchange_mode_parent = "engine.util.exchange_mode.type.Buy" - exchange_mode_location = "data/util/resource/" - - exchange_mode_ref_in_modpack = "util.resource.market_trading.MarketBuyExchangeMode" - exchange_mode_raw_api_object = RawAPIObject(exchange_mode_ref_in_modpack, - "MarketBuyExchangePool", - api_objects, - exchange_mode_location) - exchange_mode_raw_api_object.set_filename("market_trading") - exchange_mode_raw_api_object.add_raw_parent(exchange_mode_parent) - - # Fee (30% on top) - exchange_mode_raw_api_object.add_raw_member("fee_multiplier", - 1.3, - "engine.util.exchange_mode.ExchangeMode") - - pregen_converter_group.add_raw_api_object(exchange_mode_raw_api_object) - pregen_nyan_objects.update({exchange_mode_ref_in_modpack: exchange_mode_raw_api_object}) - - # ======================================================================= - # Exchange mode Sell - # ======================================================================= - exchange_mode_parent = "engine.util.exchange_mode.type.Sell" - exchange_mode_location = "data/util/resource/" - - exchange_mode_ref_in_modpack = "util.resource.market_trading.MarketSellExchangeMode" - exchange_mode_raw_api_object = RawAPIObject(exchange_mode_ref_in_modpack, - "MarketSellExchangeMode", - api_objects, - exchange_mode_location) - exchange_mode_raw_api_object.set_filename("market_trading") - exchange_mode_raw_api_object.add_raw_parent(exchange_mode_parent) - - # Fee (30% reduced) - exchange_mode_raw_api_object.add_raw_member("fee_multiplier", - 0.7, - "engine.util.exchange_mode.ExchangeMode") - - pregen_converter_group.add_raw_api_object(exchange_mode_raw_api_object) - pregen_nyan_objects.update({exchange_mode_ref_in_modpack: exchange_mode_raw_api_object}) - - # ======================================================================= - # Market Food price pool - # ======================================================================= - exchange_pool_parent = "engine.util.price_pool.PricePool" - exchange_pool_location = "data/util/resource/" - - exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketFoodPricePool" - exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, - "MarketFoodPricePool", - api_objects, - exchange_pool_location) - exchange_pool_raw_api_object.set_filename("market_trading") - exchange_pool_raw_api_object.add_raw_parent(exchange_pool_parent) - - pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) - pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) - - # ======================================================================= - # Market Carbon price pool - # ======================================================================= - exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketCarbonPricePool" - exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, - "MarketCarbonPricePool", - api_objects, - exchange_pool_location) - exchange_pool_raw_api_object.set_filename("market_trading") - exchange_pool_raw_api_object.add_raw_parent(exchange_pool_parent) - - pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) - pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) - - # ======================================================================= - # Market Ore price pool - # ======================================================================= - exchange_pool_ref_in_modpack = "util.resource.market_trading.MarketOrePricePool" - exchange_pool_raw_api_object = RawAPIObject(exchange_pool_ref_in_modpack, - "MarketOrePricePool", - api_objects, - exchange_pool_location) - exchange_pool_raw_api_object.set_filename("market_trading") - exchange_pool_raw_api_object.add_raw_parent(exchange_pool_parent) - - pregen_converter_group.add_raw_api_object(exchange_pool_raw_api_object) - pregen_nyan_objects.update({exchange_pool_ref_in_modpack: exchange_pool_raw_api_object}) - - # ======================================================================= - # Exchange rate Food - # ======================================================================= - exchange_rate_parent = "engine.util.exchange_rate.ExchangeRate" - exchange_rate_location = "data/util/resource/" - - exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketFoodExchangeRate" - exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, - "MarketFoodExchangeRate", - api_objects, - exchange_rate_location) - exchange_rate_raw_api_object.set_filename("market_trading") - exchange_rate_raw_api_object.add_raw_parent(exchange_rate_parent) - - # Base price - exchange_rate_raw_api_object.add_raw_member("base_price", - 1.0, - exchange_rate_parent) - - # Price adjust methods - pa_buy_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketBuyPriceMode") - pa_sell_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketSellPriceMode") - price_adjust = { - api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, - api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref - } - exchange_rate_raw_api_object.add_raw_member("price_adjust", - price_adjust, - exchange_rate_parent) - - # Price pool - pool_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketFoodPricePool") - exchange_rate_raw_api_object.add_raw_member("price_pool", - pool_forward_ref, - exchange_rate_parent) - - pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) - pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) - - # ======================================================================= - # Exchange rate Carbon - # ======================================================================= - exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketCarbonExchangeRate" - exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, - "MarketCarbonExchangeRate", - api_objects, - exchange_rate_location) - exchange_rate_raw_api_object.set_filename("market_trading") - exchange_rate_raw_api_object.add_raw_parent(exchange_rate_parent) - - # Base price - exchange_rate_raw_api_object.add_raw_member("base_price", - 1.0, - exchange_rate_parent) - - # Price adjust methods - pa_buy_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketBuyPriceMode") - pa_sell_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketSellPriceMode") - price_adjust = { - api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, - api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref - } - exchange_rate_raw_api_object.add_raw_member("price_adjust", - price_adjust, - exchange_rate_parent) - - # Price pool - pool_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketCarbonPricePool") - exchange_rate_raw_api_object.add_raw_member("price_pool", - pool_forward_ref, - exchange_rate_parent) - - pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) - pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) - - # ======================================================================= - # Exchange rate Ore - # ======================================================================= - exchange_rate_ref_in_modpack = "util.resource.market_trading.MarketOreExchangeRate" - exchange_rate_raw_api_object = RawAPIObject(exchange_rate_ref_in_modpack, - "MarketOreExchangeRate", - api_objects, - exchange_rate_location) - exchange_rate_raw_api_object.set_filename("market_trading") - exchange_rate_raw_api_object.add_raw_parent(exchange_rate_parent) - - # Base price - exchange_rate_raw_api_object.add_raw_member("base_price", - 1.3, - exchange_rate_parent) - - # Price adjust methods - pa_buy_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketBuyPriceMode") - pa_sell_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketSellPriceMode") - price_adjust = { - api_objects["engine.util.exchange_mode.type.Buy"]: pa_buy_forward_ref, - api_objects["engine.util.exchange_mode.type.Sell"]: pa_sell_forward_ref - } - exchange_rate_raw_api_object.add_raw_member("price_adjust", - price_adjust, - exchange_rate_parent) - - # Price pool - pool_forward_ref = ForwardRef(pregen_converter_group, - "util.resource.market_trading.MarketOrePricePool") - exchange_rate_raw_api_object.add_raw_member("price_pool", - pool_forward_ref, - exchange_rate_parent) - - pregen_converter_group.add_raw_api_object(exchange_rate_raw_api_object) - pregen_nyan_objects.update({exchange_rate_ref_in_modpack: exchange_rate_raw_api_object}) - - # ======================================================================= - # Buy Price mode - # ======================================================================= - price_mode_parent = "engine.util.price_mode.type.Dynamic" - price_mode_location = "data/util/resource/" - - price_mode_ref_in_modpack = "util.resource.market_trading.MarketBuyPriceMode" - price_mode_raw_api_object = RawAPIObject(price_mode_ref_in_modpack, - "MarketBuyPriceMode", - api_objects, - price_mode_location) - price_mode_raw_api_object.set_filename("market_trading") - price_mode_raw_api_object.add_raw_parent(price_mode_parent) - - # Change value - price_mode_raw_api_object.add_raw_member("change_value", - 0.03, - price_mode_parent) - - # Min price - price_mode_raw_api_object.add_raw_member("min_price", - 0.3, - price_mode_parent) - - # Max price - price_mode_raw_api_object.add_raw_member("max_price", - 99.9, - price_mode_parent) - - pregen_converter_group.add_raw_api_object(price_mode_raw_api_object) - pregen_nyan_objects.update({price_mode_ref_in_modpack: price_mode_raw_api_object}) - - # ======================================================================= - # Sell Price mode - # ======================================================================= - price_mode_parent = "engine.util.price_mode.type.Dynamic" - price_mode_location = "data/util/resource/" - - price_mode_ref_in_modpack = "util.resource.market_trading.MarketSellPriceMode" - price_mode_raw_api_object = RawAPIObject(price_mode_ref_in_modpack, - "MarketSellPriceMode", - api_objects, - price_mode_location) - price_mode_raw_api_object.set_filename("market_trading") - price_mode_raw_api_object.add_raw_parent(price_mode_parent) - - # Change value - price_mode_raw_api_object.add_raw_member("change_value", - -0.03, - price_mode_parent) - - # Min price - price_mode_raw_api_object.add_raw_member("min_price", - 0.3, - price_mode_parent) - - # Max price - price_mode_raw_api_object.add_raw_member("max_price", - 99.9, - price_mode_parent) - - pregen_converter_group.add_raw_api_object(price_mode_raw_api_object) - pregen_nyan_objects.update({price_mode_ref_in_modpack: price_mode_raw_api_object}) - - @staticmethod - def generate_resources( - full_data_set: GenieObjectContainer, - pregen_converter_group: ConverterObjectGroup - ) -> None: - """ - Generate Attribute objects. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer - :param pregen_converter_group: GenieObjectGroup instance that stores - pregenerated API objects for referencing with - ForwardRef - :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup - """ - pregen_nyan_objects = full_data_set.pregen_nyan_objects - api_objects = full_data_set.nyan_api_objects - - resource_parent = "engine.util.resource.Resource" - resources_location = "data/util/resource/" - - # ======================================================================= - # Food - # ======================================================================= - food_ref_in_modpack = "util.resource.types.Food" - food_raw_api_object = RawAPIObject(food_ref_in_modpack, - "Food", api_objects, - resources_location) - food_raw_api_object.set_filename("types") - food_raw_api_object.add_raw_parent(resource_parent) - - pregen_converter_group.add_raw_api_object(food_raw_api_object) - pregen_nyan_objects.update({food_ref_in_modpack: food_raw_api_object}) - - food_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - food_name_ref_in_modpack = "util.attribute.types.Food.FoodName" - food_name_value = RawAPIObject(food_name_ref_in_modpack, "FoodName", - api_objects, resources_location) - food_name_value.set_filename("types") - food_name_value.add_raw_parent(name_value_parent) - food_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - food_name_ref_in_modpack) - food_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - - pregen_converter_group.add_raw_api_object(food_name_value) - pregen_nyan_objects.update({food_name_ref_in_modpack: food_name_value}) - - # ======================================================================= - # Carbon - # ======================================================================= - carbon_ref_in_modpack = "util.resource.types.Carbon" - carbon_raw_api_object = RawAPIObject(carbon_ref_in_modpack, - "Carbon", api_objects, - resources_location) - carbon_raw_api_object.set_filename("types") - carbon_raw_api_object.add_raw_parent(resource_parent) - - pregen_converter_group.add_raw_api_object(carbon_raw_api_object) - pregen_nyan_objects.update({carbon_ref_in_modpack: carbon_raw_api_object}) - - carbon_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - carbon_name_ref_in_modpack = "util.attribute.types.Carbon.CarbonName" - carbon_name_value = RawAPIObject(carbon_name_ref_in_modpack, "CarbonName", - api_objects, resources_location) - carbon_name_value.set_filename("types") - carbon_name_value.add_raw_parent(name_value_parent) - carbon_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - carbon_name_ref_in_modpack) - carbon_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - - pregen_converter_group.add_raw_api_object(carbon_name_value) - pregen_nyan_objects.update({carbon_name_ref_in_modpack: carbon_name_value}) - - # ======================================================================= - # Ore - # ======================================================================= - ore_ref_in_modpack = "util.resource.types.Ore" - ore_raw_api_object = RawAPIObject(ore_ref_in_modpack, - "Ore", api_objects, - resources_location) - ore_raw_api_object.set_filename("types") - ore_raw_api_object.add_raw_parent(resource_parent) - - pregen_converter_group.add_raw_api_object(ore_raw_api_object) - pregen_nyan_objects.update({ore_ref_in_modpack: ore_raw_api_object}) - - ore_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - ore_name_ref_in_modpack = "util.attribute.types.Ore.OreName" - ore_name_value = RawAPIObject(ore_name_ref_in_modpack, "OreName", - api_objects, resources_location) - ore_name_value.set_filename("types") - ore_name_value.add_raw_parent(name_value_parent) - ore_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - ore_name_ref_in_modpack) - ore_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - - pregen_converter_group.add_raw_api_object(ore_name_value) - pregen_nyan_objects.update({ore_name_ref_in_modpack: ore_name_value}) - - # ======================================================================= - # Nova - # ======================================================================= - nova_ref_in_modpack = "util.resource.types.Nova" - nova_raw_api_object = RawAPIObject(nova_ref_in_modpack, - "Nova", api_objects, - resources_location) - nova_raw_api_object.set_filename("types") - nova_raw_api_object.add_raw_parent(resource_parent) - - pregen_converter_group.add_raw_api_object(nova_raw_api_object) - pregen_nyan_objects.update({nova_ref_in_modpack: nova_raw_api_object}) - - nova_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - nova_name_ref_in_modpack = "util.attribute.types.Nova.NovaName" - nova_name_value = RawAPIObject(nova_name_ref_in_modpack, "NovaName", - api_objects, resources_location) - nova_name_value.set_filename("types") - nova_name_value.add_raw_parent(name_value_parent) - nova_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - nova_name_ref_in_modpack) - nova_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - - pregen_converter_group.add_raw_api_object(nova_name_value) - pregen_nyan_objects.update({nova_name_ref_in_modpack: nova_name_value}) - - # ======================================================================= - # Population Space - # ======================================================================= - resource_contingent_parent = "engine.util.resource.ResourceContingent" - - pop_ref_in_modpack = "util.resource.types.PopulationSpace" - pop_raw_api_object = RawAPIObject(pop_ref_in_modpack, - "PopulationSpace", api_objects, - resources_location) - pop_raw_api_object.set_filename("types") - pop_raw_api_object.add_raw_parent(resource_contingent_parent) - - pregen_converter_group.add_raw_api_object(pop_raw_api_object) - pregen_nyan_objects.update({pop_ref_in_modpack: pop_raw_api_object}) - - name_value_parent = "engine.util.language.translated.type.TranslatedString" - pop_name_ref_in_modpack = "util.attribute.types.PopulationSpace.PopulationSpaceName" - pop_name_value = RawAPIObject(pop_name_ref_in_modpack, "PopulationSpaceName", - api_objects, resources_location) - pop_name_value.set_filename("types") - pop_name_value.add_raw_parent(name_value_parent) - pop_name_value.add_raw_member("translations", [], name_value_parent) - - name_forward_ref = ForwardRef(pregen_converter_group, - pop_name_ref_in_modpack) - pop_raw_api_object.add_raw_member("name", - name_forward_ref, - resource_parent) - pop_raw_api_object.add_raw_member("max_storage", - MemberSpecialValue.NYAN_INF, - resource_parent) - pop_raw_api_object.add_raw_member("min_amount", - 0, - resource_contingent_parent) - pop_raw_api_object.add_raw_member("max_amount", - 200, - resource_contingent_parent) - - pregen_converter_group.add_raw_api_object(pop_name_value) - pregen_nyan_objects.update({pop_name_ref_in_modpack: pop_name_value}) + generate_effect_types = staticmethod(generate_effect_types) + generate_exchange_objects = staticmethod(generate_exchange_objects) + generate_resources = staticmethod(generate_resources) From fa42342613226143e7e452d7223a6be36c7ce5ef Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 18:21:37 +0200 Subject: [PATCH 150/163] convert: Refactor SWGBCCProcessor into separate files. --- .../conversion/aoc/main/groups/unit_line.py | 1 - .../conversion/swgbcc/CMakeLists.txt | 1 + .../conversion/swgbcc/main/CMakeLists.txt | 6 + .../conversion/swgbcc/main/__init__.py | 5 + .../swgbcc/main/groups/CMakeLists.txt | 9 + .../conversion/swgbcc/main/groups/__init__.py | 5 + .../swgbcc/main/groups/ambient_group.py | 31 + .../swgbcc/main/groups/building_line.py | 174 ++++ .../swgbcc/main/groups/tech_group.py | 184 ++++ .../swgbcc/main/groups/unit_line.py | 173 ++++ .../swgbcc/main/groups/variant_group.py | 32 + .../swgbcc/main/groups/villager_group.py | 72 ++ .../swgbcc/main/link/CMakeLists.txt | 5 + .../conversion/swgbcc/main/link/__init__.py | 5 + .../conversion/swgbcc/main/link/garrison.py | 129 +++ .../conversion/swgbcc/main/link/repairable.py | 54 ++ .../conversion/swgbcc/pregen_subprocessor.py | 4 +- .../processor/conversion/swgbcc/processor.py | 845 ++---------------- 18 files changed, 937 insertions(+), 798 deletions(-) create mode 100644 openage/convert/processor/conversion/swgbcc/main/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/main/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/groups/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/main/groups/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/groups/ambient_group.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/groups/building_line.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/groups/tech_group.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/groups/unit_line.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/groups/variant_group.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/link/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/main/link/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/link/garrison.py create mode 100644 openage/convert/processor/conversion/swgbcc/main/link/repairable.py diff --git a/openage/convert/processor/conversion/aoc/main/groups/unit_line.py b/openage/convert/processor/conversion/aoc/main/groups/unit_line.py index c7dd1db5b4..6b5176b9c4 100644 --- a/openage/convert/processor/conversion/aoc/main/groups/unit_line.py +++ b/openage/convert/processor/conversion/aoc/main/groups/unit_line.py @@ -113,7 +113,6 @@ def create_unit_lines(full_data_set: GenieObjectContainer) -> None: full_data_set.unit_ref.update({unit_id: unit_line}) -@staticmethod def create_extra_unit_lines(full_data_set: GenieObjectContainer) -> None: """ Create additional units that are not in the unit connections. diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index 494e4d17d8..b4e8e47bbe 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -15,5 +15,6 @@ add_py_modules( add_subdirectory(ability) add_subdirectory(auxiliary) add_subdirectory(civ) +add_subdirectory(main) add_subdirectory(nyan) add_subdirectory(pregen) diff --git a/openage/convert/processor/conversion/swgbcc/main/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/main/CMakeLists.txt new file mode 100644 index 0000000000..1a89db50b8 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/CMakeLists.txt @@ -0,0 +1,6 @@ +add_py_modules( + __init__.py +) + +add_subdirectory(groups) +add_subdirectory(link) diff --git a/openage/convert/processor/conversion/swgbcc/main/__init__.py b/openage/convert/processor/conversion/swgbcc/main/__init__.py new file mode 100644 index 0000000000..abe77fc3eb --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Routines for the main SWGB conversion process. +""" diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/main/groups/CMakeLists.txt new file mode 100644 index 0000000000..c4341cf903 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/CMakeLists.txt @@ -0,0 +1,9 @@ +add_py_modules( + __init__.py + ambient_group.py + building_line.py + tech_group.py + unit_line.py + variant_group.py + villager_group.py +) diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/__init__.py b/openage/convert/processor/conversion/swgbcc/main/groups/__init__.py new file mode 100644 index 0000000000..85f22f9b5e --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create lines and groups fron extracted SWGB data. +""" diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/ambient_group.py b/openage/convert/processor/conversion/swgbcc/main/groups/ambient_group.py new file mode 100644 index 0000000000..02e5052e33 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/ambient_group.py @@ -0,0 +1,31 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create ambient groups from genie units. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieAmbientGroup +from ......value_object.conversion.swgb.internal_nyan_names import AMBIENT_GROUP_LOOKUPS + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_ambient_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create ambient groups, mostly for resources and scenery. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + ambient_ids = AMBIENT_GROUP_LOOKUPS.keys() + genie_units = full_data_set.genie_units + + for ambient_id in ambient_ids: + ambient_group = GenieAmbientGroup(ambient_id, full_data_set) + ambient_group.add_unit(genie_units[ambient_id]) + full_data_set.ambient_groups.update({ambient_group.get_id(): ambient_group}) + full_data_set.unit_ref.update({ambient_id: ambient_group}) diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/building_line.py b/openage/convert/processor/conversion/swgbcc/main/groups/building_line.py new file mode 100644 index 0000000000..642f5c3119 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/building_line.py @@ -0,0 +1,174 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create building lines from genie buildings. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_tech import BuildingUnlock +from ......entity_object.conversion.aoc.genie_tech import BuildingLineUpgrade +from ......entity_object.conversion.aoc.genie_unit import GenieBuildingLineGroup +from ......entity_object.conversion.swgbcc.genie_unit import SWGBStackBuildingGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_building_lines(full_data_set: GenieObjectContainer) -> None: + """ + Establish building lines, based on information in the building connections. + Because of how Genie building lines work, this will only find the first + building in the line. Subsequent buildings in the line have to be determined + by effects in AgeUpTechs. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + building_connections = full_data_set.building_connections + genie_techs = full_data_set.genie_techs + + # Unlocked = first in line + unlocked_by_tech = set() + + # Upgraded = later in line + upgraded_by_tech = {} + + # Search all techs for building upgrades. This is necessary because they are + # not stored in tech connections in SWGB + for tech_id, tech in genie_techs.items(): + tech_effect_id = tech["tech_effect_id"].value + if tech_effect_id < 0: + continue + + tech_effects = full_data_set.genie_effect_bundles[tech_effect_id].get_effects() + + # Search for upgrade or unlock effects + age_up = False + for effect in tech_effects: + effect_type = effect["type_id"].value + unit_id_a = effect["attr_a"].value + unit_id_b = effect["attr_b"].value + + if effect_type == 1 and effect["attr_a"].value == 6: + # if this is an age up tech, we do not need to create any additional + # unlock techs + age_up = True + + if effect_type == 2 and unit_id_a in building_connections.keys(): + # Unlocks + unlocked_by_tech.add(unit_id_a) + + if not age_up: + # Add an unlock tech group to the data set + building_unlock = BuildingUnlock(tech_id, unit_id_a, full_data_set) + full_data_set.building_unlocks.update( + {building_unlock.get_id(): building_unlock}) + + elif effect_type == 2 and unit_id_a in full_data_set.genie_units.keys(): + # Check if this is a stacked unit (gate or command center) + # for these units, we needs the stack_unit_id + building = full_data_set.genie_units[unit_id_a] + + if building.has_member("stack_unit_id") and \ + building["stack_unit_id"].value > -1: + unit_id_a = building["stack_unit_id"].value + unlocked_by_tech.add(unit_id_a) + + if not age_up: + building_unlock = BuildingUnlock(tech_id, unit_id_a, full_data_set) + full_data_set.building_unlocks.update( + {building_unlock.get_id(): building_unlock}) + + if effect_type == 3 and unit_id_b in building_connections.keys(): + # Upgrades + upgraded_by_tech[unit_id_b] = tech_id + + # First only handle the line heads (first buildings in a line) + for connection in building_connections.values(): + building_id = connection["id"].value + + if building_id not in unlocked_by_tech: + continue + + building = full_data_set.genie_units[building_id] + + # Check if we have to create a GenieStackBuildingGroup + if building.has_member("stack_unit_id") and \ + building["stack_unit_id"].value > -1: + # we don't care about head units because we process + # them with their stack unit + continue + + if building.has_member("head_unit_id") and \ + building["head_unit_id"].value > -1: + head_unit_id = building["head_unit_id"].value + building_line = SWGBStackBuildingGroup(building_id, head_unit_id, full_data_set) + + else: + building_line = GenieBuildingLineGroup(building_id, full_data_set) + + building_line.add_unit(building) + full_data_set.building_lines.update({building_line.get_id(): building_line}) + full_data_set.unit_ref.update({building_id: building_line}) + + # Second, handle all upgraded buildings + for connection in building_connections.values(): + building_id = connection["id"].value + + if building_id not in upgraded_by_tech: + continue + + building = full_data_set.genie_units[building_id] + + # Search other_connections for the previous unit in line + connected_types = connection["other_connections"].value + for index, _ in enumerate(connected_types): + connected_type = connected_types[index]["other_connection"].value + if connected_type == 1: + # 1 == Building + connected_index = index + break + + else: + raise ValueError(f"Building {building_id} is not first in line, but no previous " + "building can be found in other_connections") + + connected_ids = connection["other_connected_ids"].value + previous_unit_id = connected_ids[connected_index].value + + # Search for the first unit ID in the line recursively + previous_id = previous_unit_id + previous_connection = building_connections[previous_unit_id] + while previous_connection["line_mode"] != 2: + if previous_id in full_data_set.unit_ref.keys(): + # Short-circuit here, if we the previous unit was already handled + break + + connected_types = previous_connection["other_connections"].value + connected_index = -1 + for index, _ in enumerate(connected_types): + connected_type = connected_types[index]["other_connection"].value + if connected_type == 1: + # 1 == Building + connected_index = index + break + + connected_ids = previous_connection["other_connected_ids"].value + previous_id = connected_ids[connected_index].value + previous_connection = building_connections[previous_id] + + building_line = full_data_set.unit_ref[previous_id] + building_line.add_unit(building, after=previous_unit_id) + full_data_set.unit_ref.update({building_id: building_line}) + + # Also add the building upgrade tech here + building_upgrade = BuildingLineUpgrade( + upgraded_by_tech[building_id], + building_line.get_id(), + building_id, + full_data_set + ) + full_data_set.tech_groups.update({building_upgrade.get_id(): building_upgrade}) + full_data_set.building_upgrades.update({building_upgrade.get_id(): building_upgrade}) diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/tech_group.py b/openage/convert/processor/conversion/swgbcc/main/groups/tech_group.py new file mode 100644 index 0000000000..7f8de8a929 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/tech_group.py @@ -0,0 +1,184 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create tech groups from genie techs. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_tech import AgeUpgrade, StatUpgrade, InitiatedTech, CivBonus +from ......entity_object.conversion.swgbcc.genie_tech import SWGBUnitUnlock, SWGBUnitLineUpgrade +from ......entity_object.conversion.swgbcc.genie_unit import SWGBUnitTransformGroup +from ......value_object.conversion.swgb.internal_nyan_names import CIV_LINE_ASSOCS, CIV_TECH_ASSOCS + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_tech_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create techs from tech connections and unit upgrades / unlocks + from unit connections. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + tech_connections = full_data_set.tech_connections + + for connection in tech_connections.values(): + tech_id = connection["id"].value + tech = full_data_set.genie_techs[tech_id] + + effect_id = tech["tech_effect_id"].value + if effect_id < 0: + continue + + tech_effects = full_data_set.genie_effect_bundles[effect_id] + + # Check if the tech is an age upgrade + tech_found = False + resource_effects = tech_effects.get_effects(effect_type=1) + for effect in resource_effects: + # Resource ID 6: Current Age + if effect["attr_a"].value != 6: + continue + + age_id = effect["attr_b"].value + age_up = AgeUpgrade(tech_id, age_id, full_data_set) + full_data_set.tech_groups.update({age_up.get_id(): age_up}) + full_data_set.age_upgrades.update({age_up.get_id(): age_up}) + tech_found = True + break + + if tech_found: + continue + + # Building unlocks/upgrades are not in SWGB tech connections + + # Create a stat upgrade for other techs + stat_up = StatUpgrade(tech_id, full_data_set) + full_data_set.tech_groups.update({stat_up.get_id(): stat_up}) + full_data_set.stat_upgrades.update({stat_up.get_id(): stat_up}) + + # Unit upgrades and unlocks are stored in unit connections + unit_connections = full_data_set.unit_connections + unit_unlocks = {} + unit_upgrades = {} + for connection in unit_connections.values(): + unit_id = connection["id"].value + required_research_id = connection["required_research"].value + enabling_research_id = connection["enabling_research"].value + line_mode = connection["line_mode"].value + line_id = full_data_set.unit_ref[unit_id].get_id() + + if required_research_id == -1 and enabling_research_id == -1: + # Unit is unlocked from the start + continue + + if line_mode == 2: + # Unit is first in line, there should be an unlock tech + unit_unlock = SWGBUnitUnlock(enabling_research_id, line_id, full_data_set) + unit_unlocks.update({unit_id: unit_unlock}) + + elif line_mode == 3: + # Units further down the line receive line upgrades + unit_upgrade = SWGBUnitLineUpgrade(required_research_id, line_id, + unit_id, full_data_set) + unit_upgrades.update({required_research_id: unit_upgrade}) + + # Unit unlocks for civ lines + final_unit_unlocks = {} + for unit_unlock in unit_unlocks.values(): + line = unit_unlock.get_unlocked_line() + if line.get_head_unit_id() not in CIV_LINE_ASSOCS: + for main_head_unit_id, civ_head_unit_ids in CIV_LINE_ASSOCS.items(): + if line.get_head_unit_id() in civ_head_unit_ids: + if isinstance(line, SWGBUnitTransformGroup): + main_head_unit_id = line.get_transform_unit_id() + + # The line is an alternative civ line so the unlock + # is stored with the main unlock + main_unlock = unit_unlocks[main_head_unit_id] + main_unlock.add_civ_unlock(unit_unlock) + break + + else: + # The unlock is for a line without alternative civ lines + final_unit_unlocks.update({unit_unlock.get_id(): unit_unlock}) + + else: + # The unlock is for a main line + final_unit_unlocks.update({unit_unlock.get_id(): unit_unlock}) + + full_data_set.tech_groups.update(final_unit_unlocks) + full_data_set.unit_unlocks.update(final_unit_unlocks) + + # Unit upgrades for civ lines + final_unit_upgrades = {} + for unit_upgrade in unit_upgrades.values(): + tech_id = unit_upgrade.tech.get_id() + if tech_id not in CIV_TECH_ASSOCS: + for main_tech_id, civ_tech_ids in CIV_TECH_ASSOCS.items(): + if tech_id in civ_tech_ids: + # The tech is upgrade for an alternative civ so the upgrade + # is stored with the main upgrade + main_upgrade = unit_upgrades[main_tech_id] + main_upgrade.add_civ_upgrade(unit_upgrade) + break + + else: + # The upgrade is for a line without alternative civ lines + final_unit_upgrades.update({unit_upgrade.get_id(): unit_upgrade}) + + else: + # The upgrade is for a main line + final_unit_upgrades.update({unit_upgrade.get_id(): unit_upgrade}) + + full_data_set.tech_groups.update(final_unit_upgrades) + full_data_set.unit_upgrades.update(final_unit_upgrades) + + # Initiated techs are stored with buildings + genie_units = full_data_set.genie_units + for genie_unit in genie_units.values(): + if not genie_unit.has_member("research_id"): + continue + + building_id = genie_unit["id0"].value + initiated_tech_id = genie_unit["research_id"].value + + if initiated_tech_id == -1: + continue + + if building_id not in full_data_set.building_lines.keys(): + # Skips upgraded buildings (which initiate the same techs) + continue + + initiated_tech = InitiatedTech(initiated_tech_id, building_id, full_data_set) + full_data_set.tech_groups.update({initiated_tech.get_id(): initiated_tech}) + full_data_set.initiated_techs.update({initiated_tech.get_id(): initiated_tech}) + + # Civ boni have to be aquired from techs + # Civ boni = ONLY passive boni (not unit unlocks, unit upgrades or team bonus) + genie_techs = full_data_set.genie_techs + for index, _ in enumerate(genie_techs): + tech_id = index + + # Civ ID must be positive and non-zero + civ_id = genie_techs[index]["civilization_id"].value + if civ_id <= 0: + continue + + # Passive boni are not researched anywhere + research_location_id = genie_techs[index]["research_location_id"].value + if research_location_id > 0: + continue + + # Passive boni are not available in full tech mode + full_tech_mode = genie_techs[index]["full_tech_mode"].value + if full_tech_mode: + continue + + civ_bonus = CivBonus(tech_id, civ_id, full_data_set) + full_data_set.tech_groups.update({civ_bonus.get_id(): civ_bonus}) + full_data_set.civ_boni.update({civ_bonus.get_id(): civ_bonus}) diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/unit_line.py b/openage/convert/processor/conversion/swgbcc/main/groups/unit_line.py new file mode 100644 index 0000000000..7610172e75 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/unit_line.py @@ -0,0 +1,173 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create unit lines from genie units. +""" + +from __future__ import annotations +import typing + +from ......entity_object.conversion.swgbcc.genie_unit import SWGBUnitTransformGroup, \ + SWGBMonkGroup, SWGBUnitLineGroup +from ......value_object.conversion.swgb.internal_nyan_names import MONK_GROUP_ASSOCS, \ + CIV_LINE_ASSOCS + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_unit_lines(full_data_set: GenieObjectContainer) -> None: + """ + Sort units into lines, based on information in the unit connections. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + unit_connections = full_data_set.unit_connections + unit_lines = {} + unit_ref = {} + + # First only handle the line heads (first units in a line) + for connection in unit_connections.values(): + unit_id = connection["id"].value + unit = full_data_set.genie_units[unit_id] + line_mode = connection["line_mode"].value + + if line_mode != 2: + # It's an upgrade. Skip and handle later + continue + + # Check for special cases first + if unit.has_member("transform_unit_id")\ + and unit["transform_unit_id"].value > -1: + # Cannon + # SWGB stores the deployed cannon in the connections, but we + # want the undeployed cannon + transform_id = unit["transform_unit_id"].value + unit_line = SWGBUnitTransformGroup(transform_id, transform_id, full_data_set) + + elif unit_id in MONK_GROUP_ASSOCS: + # Jedi/Sith + # Switch to monk with relic is hardcoded :( + # for every civ (WTF LucasArts) + switch_unit_id = MONK_GROUP_ASSOCS[unit_id] + unit_line = SWGBMonkGroup(unit_id, unit_id, switch_unit_id, full_data_set) + + elif unit.has_member("task_group")\ + and unit["task_group"].value > 0: + # Villager + # done somewhere else because they are special^TM + continue + + else: + # Normal units + unit_line = SWGBUnitLineGroup(unit_id, full_data_set) + + unit_line.add_unit(unit) + unit_lines.update({unit_line.get_id(): unit_line}) + unit_ref.update({unit_id: unit_line}) + + # Second, handle all upgraded units + for connection in unit_connections.values(): + unit_id = connection["id"].value + unit = full_data_set.genie_units[unit_id] + line_mode = connection["line_mode"].value + + if line_mode != 3: + # This unit is not an upgrade and was handled in the last for-loop + continue + + # Search other_connections for the previous unit in line + connected_types = connection["other_connections"].value + for index, _ in enumerate(connected_types): + connected_type = connected_types[index]["other_connection"].value + if connected_type == 2: + # 2 == Unit + connected_index = index + break + + else: + raise ValueError(f"Unit {unit_id} is not first in line, but no previous " + "unit can be found in other_connections") + + connected_ids = connection["other_connected_ids"].value + previous_unit_id = connected_ids[connected_index].value + + # Search for the first unit ID in the line recursively + previous_id = previous_unit_id + previous_connection = unit_connections[previous_unit_id] + while previous_connection["line_mode"] != 2: + if previous_id in unit_ref: + # Short-circuit here, if we the previous unit was already handled + break + + connected_types = previous_connection["other_connections"].value + connected_index = -1 + for index, _ in enumerate(connected_types): + connected_type = connected_types[index]["other_connection"].value + if connected_type == 2: + # 2 == Unit + connected_index = index + break + + connected_ids = previous_connection["other_connected_ids"].value + previous_id = connected_ids[connected_index].value + previous_connection = unit_connections[previous_id] + + unit_line = unit_ref[previous_id] + unit_line.add_unit(unit, after=previous_unit_id) + + # Search for civ lines and attach them to their main line + final_unit_lines = {} + final_unit_lines.update(unit_lines) + for line in unit_lines.values(): + if line.get_head_unit_id() not in CIV_LINE_ASSOCS: + for main_head_unit_id, civ_head_unit_ids in CIV_LINE_ASSOCS.items(): + if line.get_head_unit_id() in civ_head_unit_ids: + # The line is an alternative civ line and should be stored + # with the main line only. + main_line = unit_lines[main_head_unit_id] + main_line.add_civ_line(line) + + # Remove the line from the main reference dict, so that + # it doesn't get converted to a game entity + final_unit_lines.pop(line.get_id()) + + # Store a reference to the main line in the unit ID refs + for unit in line.line: + full_data_set.unit_ref[unit.get_id()] = main_line + + break + + else: + # Store a reference to the line in the unit ID refs + for unit in line.line: + full_data_set.unit_ref[unit.get_id()] = line + + else: + # Store a reference to the line in the unit ID refs + for unit in line.line: + full_data_set.unit_ref[unit.get_id()] = line + + # Store the remaining lines in the main reference dict + full_data_set.unit_lines.update(final_unit_lines) + + +def create_extra_unit_lines(full_data_set: GenieObjectContainer) -> None: + """ + Create additional units that are not in the unit connections. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + # Wildlife + extra_units = (48, 594, 822, 833, 1203, 1249, 1363, 1364, + 1365, 1366, 1367, 1469, 1471, 1473, 1475) + + for unit_id in extra_units: + unit_line = SWGBUnitLineGroup(unit_id, full_data_set) + unit_line.add_unit(full_data_set.genie_units[unit_id]) + full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) + full_data_set.unit_ref.update({unit_id: unit_line}) diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/variant_group.py b/openage/convert/processor/conversion/swgbcc/main/groups/variant_group.py new file mode 100644 index 0000000000..db6ba07e2f --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/variant_group.py @@ -0,0 +1,32 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create variant groups from genie units. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieVariantGroup +from ......value_object.conversion.swgb.internal_nyan_names import VARIANT_GROUP_LOOKUPS + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_variant_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create variant groups. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + variants = VARIANT_GROUP_LOOKUPS + + for group_id, variant in variants.items(): + variant_group = GenieVariantGroup(group_id, full_data_set) + full_data_set.variant_groups.update({variant_group.get_id(): variant_group}) + + for variant_id in variant[2]: + variant_group.add_unit(full_data_set.genie_units[variant_id]) + full_data_set.unit_ref.update({variant_id: variant_group}) diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py b/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py new file mode 100644 index 0000000000..441a4fad4f --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py @@ -0,0 +1,72 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create villager groups from genie units. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieUnitTaskGroup, GenieVillagerGroup + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def create_villager_groups(full_data_set: GenieObjectContainer) -> None: + """ + Create task groups and assign the relevant worker group to a + villager group. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + units = full_data_set.genie_units + task_group_ids = set() + unit_ids = set() + + # Find task groups in the dataset + for unit in units.values(): + if unit.has_member("task_group"): + task_group_id = unit["task_group"].value + + else: + task_group_id = 0 + + if task_group_id == 0: + # no task group + continue + + if task_group_id in task_group_ids: + task_group = full_data_set.task_groups[task_group_id] + task_group.add_unit(unit) + + else: + if task_group_id == 1: + # SWGB uses the same IDs as AoC + line_id = GenieUnitTaskGroup.male_line_id + + elif task_group_id == 2: + # No differences to task group 1; probably unused + continue + + else: + raise ValueError( + f"Unknown task group ID {task_group_id} for unit {unit['id0'].value}" + ) + + task_group = GenieUnitTaskGroup(line_id, task_group_id, full_data_set) + task_group.add_unit(unit) + full_data_set.task_groups.update({task_group_id: task_group}) + + task_group_ids.add(task_group_id) + unit_ids.add(unit["id0"].value) + + # Create the villager task group + villager = GenieVillagerGroup(118, task_group_ids, full_data_set) + full_data_set.unit_lines.update({villager.get_id(): villager}) + # TODO: Find the line id elsewhere + full_data_set.unit_lines_vertical_ref.update({36: villager}) + full_data_set.villager_groups.update({villager.get_id(): villager}) + for unit_id in unit_ids: + full_data_set.unit_ref.update({unit_id: villager}) diff --git a/openage/convert/processor/conversion/swgbcc/main/link/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/main/link/CMakeLists.txt new file mode 100644 index 0000000000..24513df1c3 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/link/CMakeLists.txt @@ -0,0 +1,5 @@ +add_py_modules( + __init__.py + garrison.py + repairable.py +) diff --git a/openage/convert/processor/conversion/swgbcc/main/link/__init__.py b/openage/convert/processor/conversion/swgbcc/main/link/__init__.py new file mode 100644 index 0000000000..45f951d36f --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/link/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link SWGB data to the game entities created from lines. +""" diff --git a/openage/convert/processor/conversion/swgbcc/main/link/garrison.py b/openage/convert/processor/conversion/swgbcc/main/link/garrison.py new file mode 100644 index 0000000000..d7913a88a5 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/link/garrison.py @@ -0,0 +1,129 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link garrisonable lines to their garrison locations and vice versa. +""" +from __future__ import annotations +import typing + +from ......entity_object.conversion.aoc.genie_unit import GenieGarrisonMode + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_garrison(full_data_set: GenieObjectContainer) -> None: + """ + Link a garrison unit to the lines that are stored and vice versa. This is done + to provide quick access during conversion. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + garrisoned_lines = {} + garrisoned_lines.update(full_data_set.unit_lines) + garrisoned_lines.update(full_data_set.ambient_groups) + + garrison_lines = {} + garrison_lines.update(full_data_set.unit_lines) + garrison_lines.update(full_data_set.building_lines) + + # Search through all units and look at their garrison commands + for unit_line in garrisoned_lines.values(): + garrison_classes = [] + garrison_units = [] + + if unit_line.has_command(3): + unit_commands = unit_line.get_head_unit()["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + if type_id != 3: + continue + + class_id = command["class_id"].value + if class_id > -1: + garrison_classes.append(class_id) + + if class_id == 18: + # Towers because LucasArts ALSO didn't like consistent rules + garrison_classes.append(10) + + unit_id = command["unit_id"].value + if unit_id > -1: + garrison_units.append(unit_id) + + for garrison_line in garrison_lines.values(): + if not garrison_line.is_garrison(): + continue + + # Natural garrison + garrison_mode = garrison_line.get_garrison_mode() + if garrison_mode == GenieGarrisonMode.NATURAL: + if unit_line.get_head_unit().has_member("creatable_type"): + creatable_type = unit_line.get_head_unit()["creatable_type"].value + + else: + creatable_type = 0 + + if garrison_line.get_head_unit().has_member("garrison_type"): + garrison_type = garrison_line.get_head_unit()["garrison_type"].value + + else: + garrison_type = 0 + + if creatable_type == 1 and not garrison_type & 0x01: + continue + + if creatable_type == 2 and not garrison_type & 0x02: + continue + + if creatable_type == 3 and not garrison_type & 0x04: + continue + + if creatable_type == 6 and not garrison_type & 0x08: + continue + + if (creatable_type == 0 and unit_line.get_class_id() == 1) and not\ + garrison_type & 0x10: + # Bantha/Nerf + continue + + if garrison_line.get_class_id() in garrison_classes: + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) + continue + + if garrison_line.get_head_unit_id() in garrison_units: + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) + continue + + # Transports/ unit garrisons (no conditions) + elif garrison_mode in (GenieGarrisonMode.TRANSPORT, + GenieGarrisonMode.UNIT_GARRISON): + if garrison_line.get_class_id() in garrison_classes: + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) + + # Self produced units (these cannot be determined from commands) + elif garrison_mode == GenieGarrisonMode.SELF_PRODUCED: + if unit_line in garrison_line.creates: + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) + + # Jedi/Sith inventories + elif garrison_mode == GenieGarrisonMode.MONK: + # Search for a pickup command + unit_commands = garrison_line.get_head_unit()["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + if type_id != 132: + continue + + unit_id = command["unit_id"].value + if unit_id == unit_line.get_head_unit_id(): + unit_line.garrison_locations.append(garrison_line) + garrison_line.garrison_entities.append(unit_line) diff --git a/openage/convert/processor/conversion/swgbcc/main/link/repairable.py b/openage/convert/processor/conversion/swgbcc/main/link/repairable.py new file mode 100644 index 0000000000..abb7dde28d --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/link/repairable.py @@ -0,0 +1,54 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Link repairable units/buildings to villager groups. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from ......entity_object.conversion.aoc.genie_object_container import GenieObjectContainer + + +def link_repairables(full_data_set: GenieObjectContainer) -> None: + """ + Set units / buildings as repairable + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + villager_groups = full_data_set.villager_groups + + repair_lines = {} + repair_lines.update(full_data_set.unit_lines) + repair_lines.update(full_data_set.building_lines) + + repair_classes = [] + for villager in villager_groups.values(): + repair_unit = villager.get_units_with_command(106)[0] + unit_commands = repair_unit["unit_commands"].value + for command in unit_commands: + type_id = command["type"].value + + if type_id != 106: + continue + + class_id = command["class_id"].value + if class_id == -1: + # Buildings/Siege + repair_classes.append(10) + repair_classes.append(18) + repair_classes.append(32) + repair_classes.append(33) + repair_classes.append(34) + repair_classes.append(35) + repair_classes.append(36) + repair_classes.append(53) + + else: + repair_classes.append(class_id) + + for repair_line in repair_lines.values(): + if repair_line.get_class_id() in repair_classes: + repair_line.repairable = True diff --git a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py index ecb8454ad0..d0512d7cc9 100644 --- a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py @@ -41,8 +41,8 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: AoCPregenSubprocessor.generate_formation_types(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_language_objects(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_misc_effect_objects(full_data_set, pregen_converter_group) - # cls._generate_modifiers(gamedata, pregen_converter_group) ?? - # cls._generate_terrain_types(gamedata, pregen_converter_group) TODO: Create terrain types + # generate_modifiers(gamedata, pregen_converter_group) ?? + # generate_terrain_types(gamedata, pregen_converter_group) TODO: Create terrain types AoCPregenSubprocessor.generate_path_types(full_data_set, pregen_converter_group) generate_resources(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_death_condition(full_data_set, pregen_converter_group) diff --git a/openage/convert/processor/conversion/swgbcc/processor.py b/openage/convert/processor/conversion/swgbcc/processor.py index 8fbb9d97ae..44923cc2ec 100644 --- a/openage/convert/processor/conversion/swgbcc/processor.py +++ b/openage/convert/processor/conversion/swgbcc/processor.py @@ -1,46 +1,37 @@ -# Copyright 2020-2024 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-lines,too-many-branches,too-many-statements,too-many-locals -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. + """ -Convert data from SWGB:CC to openage formats. +Convert data from SWGB: CC to openage formats. """ from __future__ import annotations import typing - -from openage.convert.entity_object.conversion.aoc.genie_tech import BuildingUnlock from .....log import info from ....entity_object.conversion.aoc.genie_object_container import GenieObjectContainer -from ....entity_object.conversion.aoc.genie_tech import BuildingLineUpgrade, \ - AgeUpgrade, StatUpgrade, InitiatedTech, CivBonus -from ....entity_object.conversion.aoc.genie_unit import GenieUnitTaskGroup, \ - GenieVillagerGroup, GenieAmbientGroup, GenieVariantGroup, \ - GenieBuildingLineGroup, GenieGarrisonMode -from ....entity_object.conversion.swgbcc.genie_tech import SWGBUnitUnlock, \ - SWGBUnitLineUpgrade -from ....entity_object.conversion.swgbcc.genie_unit import SWGBUnitTransformGroup, \ - SWGBMonkGroup, SWGBUnitLineGroup, SWGBStackBuildingGroup -from ....service.debug_info import debug_converter_objects, \ - debug_converter_object_groups +from ....service.debug_info import debug_converter_objects, debug_converter_object_groups from ....service.read.nyan_api_loader import load_api -from ....value_object.conversion.swgb.internal_nyan_names import MONK_GROUP_ASSOCS, \ - CIV_LINE_ASSOCS, AMBIENT_GROUP_LOOKUPS, VARIANT_GROUP_LOOKUPS, \ - CIV_TECH_ASSOCS from ..aoc.media_subprocessor import AoCMediaSubprocessor from ..aoc.processor import AoCProcessor from .modpack_subprocessor import SWGBCCModpackSubprocessor from .nyan_subprocessor import SWGBCCNyanSubprocessor from .pregen_subprocessor import SWGBCCPregenSubprocessor +from .main.groups.ambient_group import create_ambient_groups +from .main.groups.building_line import create_building_lines +from .main.groups.tech_group import create_tech_groups +from .main.groups.unit_line import create_unit_lines, create_extra_unit_lines +from .main.groups.variant_group import create_variant_groups +from .main.groups.villager_group import create_villager_groups + +from .main.link.garrison import link_garrison +from .main.link.repairable import link_repairables + if typing.TYPE_CHECKING: from argparse import Namespace - from openage.convert.entity_object.conversion.stringresource import StringResource - from openage.convert.entity_object.conversion.modpack import Modpack - from openage.convert.value_object.read.value_members import ArrayMember - from openage.convert.value_object.init.game_version import GameVersion + from ....entity_object.conversion.stringresource import StringResource + from ....entity_object.conversion.modpack import Modpack + from ....value_object.read.value_members import ArrayMember + from ....value_object.init.game_version import GameVersion class SWGBCCProcessor: @@ -60,11 +51,11 @@ def convert( Input game specification and media here and get a set of modpacks back. - :param gamespec: Gamedata from empires.dat read in by the + : param gamespec: Gamedata from empires.dat read in by the reader functions. - :type gamespec: class: ...dataformat.value_members.ArrayMember - :returns: A list of modpacks. - :rtype: list + : type gamespec: class: ...dataformat.value_members.ArrayMember + : returns: A list of modpacks. + : rtype: list """ info("Starting conversion...") @@ -98,12 +89,12 @@ def _pre_processor( """ Store data from the reader in a conversion container. - :param gamespec: Gamedata from empires.dat file. - :type gamespec: class: ...dataformat.value_members.ArrayMember - :param full_data_set: GenieObjectContainer instance that + : param gamespec: Gamedata from empires.dat file. + : type gamespec: class: ...dataformat.value_members.ArrayMember + : param full_data_set: GenieObjectContainer instance that contains all relevant data for the conversion process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + : type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer """ dataset = GenieObjectContainer() @@ -133,25 +124,25 @@ def _pre_processor( @classmethod def _processor(cls, full_data_set: GenieObjectContainer) -> GenieObjectContainer: """ - Transfer structures used in Genie games to more openage-friendly + Transfer structures used in Genie games to more openage - friendly Python objects. - :param full_data_set: GenieObjectContainer instance that + : param full_data_set: GenieObjectContainer instance that contains all relevant data for the conversion process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + : type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer """ info("Creating API-like objects...") - cls.create_unit_lines(full_data_set) - cls.create_extra_unit_lines(full_data_set) - cls.create_building_lines(full_data_set) - cls.create_villager_groups(full_data_set) - cls.create_ambient_groups(full_data_set) - cls.create_variant_groups(full_data_set) + create_unit_lines(full_data_set) + create_extra_unit_lines(full_data_set) + create_building_lines(full_data_set) + create_villager_groups(full_data_set) + create_ambient_groups(full_data_set) + create_variant_groups(full_data_set) AoCProcessor.create_terrain_groups(full_data_set) - cls.create_tech_groups(full_data_set) + create_tech_groups(full_data_set) AoCProcessor.create_civ_groups(full_data_set) info("Linking API-like objects...") @@ -161,9 +152,9 @@ def _processor(cls, full_data_set: GenieObjectContainer) -> GenieObjectContainer AoCProcessor.link_researchables(full_data_set) AoCProcessor.link_civ_uniques(full_data_set) AoCProcessor.link_gatherers_to_dropsites(full_data_set) - cls.link_garrison(full_data_set) + link_garrison(full_data_set) AoCProcessor.link_trade_posts(full_data_set) - cls.link_repairables(full_data_set) + link_repairables(full_data_set) info("Generating auxiliary objects...") @@ -174,12 +165,12 @@ def _processor(cls, full_data_set: GenieObjectContainer) -> GenieObjectContainer @classmethod def _post_processor(cls, full_data_set: GenieObjectContainer) -> list[Modpack]: """ - Convert API-like Python objects to nyan. + Convert API - like Python objects to nyan. - :param full_data_set: GenieObjectContainer instance that + : param full_data_set: GenieObjectContainer instance that contains all relevant data for the conversion process. - :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer + : type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer """ info("Creating nyan objects...") @@ -192,749 +183,13 @@ def _post_processor(cls, full_data_set: GenieObjectContainer) -> list[Modpack]: return SWGBCCModpackSubprocessor.get_modpacks(full_data_set) - @staticmethod - def create_unit_lines(full_data_set: GenieObjectContainer) -> None: - """ - Sort units into lines, based on information in the unit connections. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - unit_connections = full_data_set.unit_connections - unit_lines = {} - unit_ref = {} - - # First only handle the line heads (first units in a line) - for connection in unit_connections.values(): - unit_id = connection["id"].value - unit = full_data_set.genie_units[unit_id] - line_mode = connection["line_mode"].value - - if line_mode != 2: - # It's an upgrade. Skip and handle later - continue - - # Check for special cases first - if unit.has_member("transform_unit_id")\ - and unit["transform_unit_id"].value > -1: - # Cannon - # SWGB stores the deployed cannon in the connections, but we - # want the undeployed cannon - transform_id = unit["transform_unit_id"].value - unit_line = SWGBUnitTransformGroup(transform_id, transform_id, full_data_set) - - elif unit_id in MONK_GROUP_ASSOCS: - # Jedi/Sith - # Switch to monk with relic is hardcoded :( - # for every civ (WTF LucasArts) - switch_unit_id = MONK_GROUP_ASSOCS[unit_id] - unit_line = SWGBMonkGroup(unit_id, unit_id, switch_unit_id, full_data_set) - - elif unit.has_member("task_group")\ - and unit["task_group"].value > 0: - # Villager - # done somewhere else because they are special^TM - continue - - else: - # Normal units - unit_line = SWGBUnitLineGroup(unit_id, full_data_set) - - unit_line.add_unit(unit) - unit_lines.update({unit_line.get_id(): unit_line}) - unit_ref.update({unit_id: unit_line}) - - # Second, handle all upgraded units - for connection in unit_connections.values(): - unit_id = connection["id"].value - unit = full_data_set.genie_units[unit_id] - line_mode = connection["line_mode"].value - - if line_mode != 3: - # This unit is not an upgrade and was handled in the last for-loop - continue - - # Search other_connections for the previous unit in line - connected_types = connection["other_connections"].value - for index, _ in enumerate(connected_types): - connected_type = connected_types[index]["other_connection"].value - if connected_type == 2: - # 2 == Unit - connected_index = index - break - - else: - raise ValueError(f"Unit {unit_id} is not first in line, but no previous " - "unit can be found in other_connections") - - connected_ids = connection["other_connected_ids"].value - previous_unit_id = connected_ids[connected_index].value - - # Search for the first unit ID in the line recursively - previous_id = previous_unit_id - previous_connection = unit_connections[previous_unit_id] - while previous_connection["line_mode"] != 2: - if previous_id in unit_ref: - # Short-circuit here, if we the previous unit was already handled - break - - connected_types = previous_connection["other_connections"].value - connected_index = -1 - for index, _ in enumerate(connected_types): - connected_type = connected_types[index]["other_connection"].value - if connected_type == 2: - # 2 == Unit - connected_index = index - break - - connected_ids = previous_connection["other_connected_ids"].value - previous_id = connected_ids[connected_index].value - previous_connection = unit_connections[previous_id] - - unit_line = unit_ref[previous_id] - unit_line.add_unit(unit, after=previous_unit_id) - - # Search for civ lines and attach them to their main line - final_unit_lines = {} - final_unit_lines.update(unit_lines) - for line in unit_lines.values(): - if line.get_head_unit_id() not in CIV_LINE_ASSOCS: - for main_head_unit_id, civ_head_unit_ids in CIV_LINE_ASSOCS.items(): - if line.get_head_unit_id() in civ_head_unit_ids: - # The line is an alternative civ line and should be stored - # with the main line only. - main_line = unit_lines[main_head_unit_id] - main_line.add_civ_line(line) - - # Remove the line from the main reference dict, so that - # it doesn't get converted to a game entity - final_unit_lines.pop(line.get_id()) - - # Store a reference to the main line in the unit ID refs - for unit in line.line: - full_data_set.unit_ref[unit.get_id()] = main_line - - break - - else: - # Store a reference to the line in the unit ID refs - for unit in line.line: - full_data_set.unit_ref[unit.get_id()] = line - - else: - # Store a reference to the line in the unit ID refs - for unit in line.line: - full_data_set.unit_ref[unit.get_id()] = line - - # Store the remaining lines in the main reference dict - full_data_set.unit_lines.update(final_unit_lines) - - @staticmethod - def create_extra_unit_lines(full_data_set: GenieObjectContainer) -> None: - """ - Create additional units that are not in the unit connections. + create_ambient_groups = staticmethod(create_ambient_groups) + create_building_lines = staticmethod(create_building_lines) + create_tech_groups = staticmethod(create_tech_groups) + create_unit_lines = staticmethod(create_unit_lines) + create_extra_unit_lines = staticmethod(create_extra_unit_lines) + create_variant_groups = staticmethod(create_variant_groups) + create_villager_groups = staticmethod(create_villager_groups) - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - # Wildlife - extra_units = (48, 594, 822, 833, 1203, 1249, 1363, 1364, - 1365, 1366, 1367, 1469, 1471, 1473, 1475) - - for unit_id in extra_units: - unit_line = SWGBUnitLineGroup(unit_id, full_data_set) - unit_line.add_unit(full_data_set.genie_units[unit_id]) - full_data_set.unit_lines.update({unit_line.get_id(): unit_line}) - full_data_set.unit_ref.update({unit_id: unit_line}) - - @staticmethod - def create_building_lines(full_data_set: GenieObjectContainer) -> None: - """ - Establish building lines, based on information in the building connections. - Because of how Genie building lines work, this will only find the first - building in the line. Subsequent buildings in the line have to be determined - by effects in AgeUpTechs. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - building_connections = full_data_set.building_connections - genie_techs = full_data_set.genie_techs - - # Unlocked = first in line - unlocked_by_tech = set() - - # Upgraded = later in line - upgraded_by_tech = {} - - # Search all techs for building upgrades. This is necessary because they are - # not stored in tech connections in SWGB - for tech_id, tech in genie_techs.items(): - tech_effect_id = tech["tech_effect_id"].value - if tech_effect_id < 0: - continue - - tech_effects = full_data_set.genie_effect_bundles[tech_effect_id].get_effects() - - # Search for upgrade or unlock effects - age_up = False - for effect in tech_effects: - effect_type = effect["type_id"].value - unit_id_a = effect["attr_a"].value - unit_id_b = effect["attr_b"].value - - if effect_type == 1 and effect["attr_a"].value == 6: - # if this is an age up tech, we do not need to create any additional - # unlock techs - age_up = True - - if effect_type == 2 and unit_id_a in building_connections.keys(): - # Unlocks - unlocked_by_tech.add(unit_id_a) - - if not age_up: - # Add an unlock tech group to the data set - building_unlock = BuildingUnlock(tech_id, unit_id_a, full_data_set) - full_data_set.building_unlocks.update( - {building_unlock.get_id(): building_unlock}) - - elif effect_type == 2 and unit_id_a in full_data_set.genie_units.keys(): - # Check if this is a stacked unit (gate or command center) - # for these units, we needs the stack_unit_id - building = full_data_set.genie_units[unit_id_a] - - if building.has_member("stack_unit_id") and \ - building["stack_unit_id"].value > -1: - unit_id_a = building["stack_unit_id"].value - unlocked_by_tech.add(unit_id_a) - - if not age_up: - building_unlock = BuildingUnlock(tech_id, unit_id_a, full_data_set) - full_data_set.building_unlocks.update( - {building_unlock.get_id(): building_unlock}) - - if effect_type == 3 and unit_id_b in building_connections.keys(): - # Upgrades - upgraded_by_tech[unit_id_b] = tech_id - - # First only handle the line heads (first buildings in a line) - for connection in building_connections.values(): - building_id = connection["id"].value - - if building_id not in unlocked_by_tech: - continue - - building = full_data_set.genie_units[building_id] - - # Check if we have to create a GenieStackBuildingGroup - if building.has_member("stack_unit_id") and \ - building["stack_unit_id"].value > -1: - # we don't care about head units because we process - # them with their stack unit - continue - - if building.has_member("head_unit_id") and \ - building["head_unit_id"].value > -1: - head_unit_id = building["head_unit_id"].value - building_line = SWGBStackBuildingGroup(building_id, head_unit_id, full_data_set) - - else: - building_line = GenieBuildingLineGroup(building_id, full_data_set) - - building_line.add_unit(building) - full_data_set.building_lines.update({building_line.get_id(): building_line}) - full_data_set.unit_ref.update({building_id: building_line}) - - # Second, handle all upgraded buildings - for connection in building_connections.values(): - building_id = connection["id"].value - - if building_id not in upgraded_by_tech: - continue - - building = full_data_set.genie_units[building_id] - - # Search other_connections for the previous unit in line - connected_types = connection["other_connections"].value - for index, _ in enumerate(connected_types): - connected_type = connected_types[index]["other_connection"].value - if connected_type == 1: - # 1 == Building - connected_index = index - break - - else: - raise ValueError(f"Building {building_id} is not first in line, but no previous " - "building can be found in other_connections") - - connected_ids = connection["other_connected_ids"].value - previous_unit_id = connected_ids[connected_index].value - - # Search for the first unit ID in the line recursively - previous_id = previous_unit_id - previous_connection = building_connections[previous_unit_id] - while previous_connection["line_mode"] != 2: - if previous_id in full_data_set.unit_ref.keys(): - # Short-circuit here, if we the previous unit was already handled - break - - connected_types = previous_connection["other_connections"].value - connected_index = -1 - for index, _ in enumerate(connected_types): - connected_type = connected_types[index]["other_connection"].value - if connected_type == 1: - # 1 == Building - connected_index = index - break - - connected_ids = previous_connection["other_connected_ids"].value - previous_id = connected_ids[connected_index].value - previous_connection = building_connections[previous_id] - - building_line = full_data_set.unit_ref[previous_id] - building_line.add_unit(building, after=previous_unit_id) - full_data_set.unit_ref.update({building_id: building_line}) - - # Also add the building upgrade tech here - building_upgrade = BuildingLineUpgrade( - upgraded_by_tech[building_id], - building_line.get_id(), - building_id, - full_data_set - ) - full_data_set.tech_groups.update({building_upgrade.get_id(): building_upgrade}) - full_data_set.building_upgrades.update({building_upgrade.get_id(): building_upgrade}) - - @staticmethod - def create_villager_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create task groups and assign the relevant worker group to a - villager group. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - units = full_data_set.genie_units - task_group_ids = set() - unit_ids = set() - - # Find task groups in the dataset - for unit in units.values(): - if unit.has_member("task_group"): - task_group_id = unit["task_group"].value - - else: - task_group_id = 0 - - if task_group_id == 0: - # no task group - continue - - if task_group_id in task_group_ids: - task_group = full_data_set.task_groups[task_group_id] - task_group.add_unit(unit) - - else: - if task_group_id == 1: - # SWGB uses the same IDs as AoC - line_id = GenieUnitTaskGroup.male_line_id - - elif task_group_id == 2: - # No differences to task group 1; probably unused - continue - - task_group = GenieUnitTaskGroup(line_id, task_group_id, full_data_set) - task_group.add_unit(unit) - full_data_set.task_groups.update({task_group_id: task_group}) - - task_group_ids.add(task_group_id) - unit_ids.add(unit["id0"].value) - - # Create the villager task group - villager = GenieVillagerGroup(118, task_group_ids, full_data_set) - full_data_set.unit_lines.update({villager.get_id(): villager}) - # TODO: Find the line id elsewhere - full_data_set.unit_lines_vertical_ref.update({36: villager}) - full_data_set.villager_groups.update({villager.get_id(): villager}) - for unit_id in unit_ids: - full_data_set.unit_ref.update({unit_id: villager}) - - @staticmethod - def create_ambient_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create ambient groups, mostly for resources and scenery. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - ambient_ids = AMBIENT_GROUP_LOOKUPS.keys() - genie_units = full_data_set.genie_units - - for ambient_id in ambient_ids: - ambient_group = GenieAmbientGroup(ambient_id, full_data_set) - ambient_group.add_unit(genie_units[ambient_id]) - full_data_set.ambient_groups.update({ambient_group.get_id(): ambient_group}) - full_data_set.unit_ref.update({ambient_id: ambient_group}) - - @staticmethod - def create_variant_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create variant groups. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - variants = VARIANT_GROUP_LOOKUPS - - for group_id, variant in variants.items(): - variant_group = GenieVariantGroup(group_id, full_data_set) - full_data_set.variant_groups.update({variant_group.get_id(): variant_group}) - - for variant_id in variant[2]: - variant_group.add_unit(full_data_set.genie_units[variant_id]) - full_data_set.unit_ref.update({variant_id: variant_group}) - - @staticmethod - def create_tech_groups(full_data_set: GenieObjectContainer) -> None: - """ - Create techs from tech connections and unit upgrades/unlocks - from unit connections. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - tech_connections = full_data_set.tech_connections - - for connection in tech_connections.values(): - tech_id = connection["id"].value - tech = full_data_set.genie_techs[tech_id] - - effect_id = tech["tech_effect_id"].value - if effect_id < 0: - continue - - tech_effects = full_data_set.genie_effect_bundles[effect_id] - - # Check if the tech is an age upgrade - tech_found = False - resource_effects = tech_effects.get_effects(effect_type=1) - for effect in resource_effects: - # Resource ID 6: Current Age - if effect["attr_a"].value != 6: - continue - - age_id = effect["attr_b"].value - age_up = AgeUpgrade(tech_id, age_id, full_data_set) - full_data_set.tech_groups.update({age_up.get_id(): age_up}) - full_data_set.age_upgrades.update({age_up.get_id(): age_up}) - tech_found = True - break - - if tech_found: - continue - - # Building unlocks/upgrades are not in SWGB tech connections - - # Create a stat upgrade for other techs - stat_up = StatUpgrade(tech_id, full_data_set) - full_data_set.tech_groups.update({stat_up.get_id(): stat_up}) - full_data_set.stat_upgrades.update({stat_up.get_id(): stat_up}) - - # Unit upgrades and unlocks are stored in unit connections - unit_connections = full_data_set.unit_connections - unit_unlocks = {} - unit_upgrades = {} - for connection in unit_connections.values(): - unit_id = connection["id"].value - required_research_id = connection["required_research"].value - enabling_research_id = connection["enabling_research"].value - line_mode = connection["line_mode"].value - line_id = full_data_set.unit_ref[unit_id].get_id() - - if required_research_id == -1 and enabling_research_id == -1: - # Unit is unlocked from the start - continue - - if line_mode == 2: - # Unit is first in line, there should be an unlock tech - unit_unlock = SWGBUnitUnlock(enabling_research_id, line_id, full_data_set) - unit_unlocks.update({unit_id: unit_unlock}) - - elif line_mode == 3: - # Units further down the line receive line upgrades - unit_upgrade = SWGBUnitLineUpgrade(required_research_id, line_id, - unit_id, full_data_set) - unit_upgrades.update({required_research_id: unit_upgrade}) - - # Unit unlocks for civ lines - final_unit_unlocks = {} - for unit_unlock in unit_unlocks.values(): - line = unit_unlock.get_unlocked_line() - if line.get_head_unit_id() not in CIV_LINE_ASSOCS: - for main_head_unit_id, civ_head_unit_ids in CIV_LINE_ASSOCS.items(): - if line.get_head_unit_id() in civ_head_unit_ids: - if isinstance(line, SWGBUnitTransformGroup): - main_head_unit_id = line.get_transform_unit_id() - - # The line is an alternative civ line so the unlock - # is stored with the main unlock - main_unlock = unit_unlocks[main_head_unit_id] - main_unlock.add_civ_unlock(unit_unlock) - break - - else: - # The unlock is for a line without alternative civ lines - final_unit_unlocks.update({unit_unlock.get_id(): unit_unlock}) - - else: - # The unlock is for a main line - final_unit_unlocks.update({unit_unlock.get_id(): unit_unlock}) - - full_data_set.tech_groups.update(final_unit_unlocks) - full_data_set.unit_unlocks.update(final_unit_unlocks) - - # Unit upgrades for civ lines - final_unit_upgrades = {} - for unit_upgrade in unit_upgrades.values(): - tech_id = unit_upgrade.tech.get_id() - if tech_id not in CIV_TECH_ASSOCS: - for main_tech_id, civ_tech_ids in CIV_TECH_ASSOCS.items(): - if tech_id in civ_tech_ids: - # The tech is upgrade for an alternative civ so the upgrade - # is stored with the main upgrade - main_upgrade = unit_upgrades[main_tech_id] - main_upgrade.add_civ_upgrade(unit_upgrade) - break - - else: - # The upgrade is for a line without alternative civ lines - final_unit_upgrades.update({unit_upgrade.get_id(): unit_upgrade}) - - else: - # The upgrade is for a main line - final_unit_upgrades.update({unit_upgrade.get_id(): unit_upgrade}) - - full_data_set.tech_groups.update(final_unit_upgrades) - full_data_set.unit_upgrades.update(final_unit_upgrades) - - # Initiated techs are stored with buildings - genie_units = full_data_set.genie_units - for genie_unit in genie_units.values(): - if not genie_unit.has_member("research_id"): - continue - - building_id = genie_unit["id0"].value - initiated_tech_id = genie_unit["research_id"].value - - if initiated_tech_id == -1: - continue - - if building_id not in full_data_set.building_lines.keys(): - # Skips upgraded buildings (which initiate the same techs) - continue - - initiated_tech = InitiatedTech(initiated_tech_id, building_id, full_data_set) - full_data_set.tech_groups.update({initiated_tech.get_id(): initiated_tech}) - full_data_set.initiated_techs.update({initiated_tech.get_id(): initiated_tech}) - - # Civ boni have to be aquired from techs - # Civ boni = ONLY passive boni (not unit unlocks, unit upgrades or team bonus) - genie_techs = full_data_set.genie_techs - for index, _ in enumerate(genie_techs): - tech_id = index - - # Civ ID must be positive and non-zero - civ_id = genie_techs[index]["civilization_id"].value - if civ_id <= 0: - continue - - # Passive boni are not researched anywhere - research_location_id = genie_techs[index]["research_location_id"].value - if research_location_id > 0: - continue - - # Passive boni are not available in full tech mode - full_tech_mode = genie_techs[index]["full_tech_mode"].value - if full_tech_mode: - continue - - civ_bonus = CivBonus(tech_id, civ_id, full_data_set) - full_data_set.tech_groups.update({civ_bonus.get_id(): civ_bonus}) - full_data_set.civ_boni.update({civ_bonus.get_id(): civ_bonus}) - - @staticmethod - def link_garrison(full_data_set: GenieObjectContainer) -> None: - """ - Link a garrison unit to the lines that are stored and vice versa. This is done - to provide quick access during conversion. - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - garrisoned_lines = {} - garrisoned_lines.update(full_data_set.unit_lines) - garrisoned_lines.update(full_data_set.ambient_groups) - - garrison_lines = {} - garrison_lines.update(full_data_set.unit_lines) - garrison_lines.update(full_data_set.building_lines) - - # Search through all units and look at their garrison commands - for unit_line in garrisoned_lines.values(): - garrison_classes = [] - garrison_units = [] - - if unit_line.has_command(3): - unit_commands = unit_line.get_head_unit()["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - if type_id != 3: - continue - - class_id = command["class_id"].value - if class_id > -1: - garrison_classes.append(class_id) - - if class_id == 18: - # Towers because LucasArts ALSO didn't like consistent rules - garrison_classes.append(10) - - unit_id = command["unit_id"].value - if unit_id > -1: - garrison_units.append(unit_id) - - for garrison_line in garrison_lines.values(): - if not garrison_line.is_garrison(): - continue - - # Natural garrison - garrison_mode = garrison_line.get_garrison_mode() - if garrison_mode == GenieGarrisonMode.NATURAL: - if unit_line.get_head_unit().has_member("creatable_type"): - creatable_type = unit_line.get_head_unit()["creatable_type"].value - - else: - creatable_type = 0 - - if garrison_line.get_head_unit().has_member("garrison_type"): - garrison_type = garrison_line.get_head_unit()["garrison_type"].value - - else: - garrison_type = 0 - - if creatable_type == 1 and not garrison_type & 0x01: - continue - - if creatable_type == 2 and not garrison_type & 0x02: - continue - - if creatable_type == 3 and not garrison_type & 0x04: - continue - - if creatable_type == 6 and not garrison_type & 0x08: - continue - - if (creatable_type == 0 and unit_line.get_class_id() == 1) and not\ - garrison_type & 0x10: - # Bantha/Nerf - continue - - if garrison_line.get_class_id() in garrison_classes: - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - continue - - if garrison_line.get_head_unit_id() in garrison_units: - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - continue - - # Transports/ unit garrisons (no conditions) - elif garrison_mode in (GenieGarrisonMode.TRANSPORT, - GenieGarrisonMode.UNIT_GARRISON): - if garrison_line.get_class_id() in garrison_classes: - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - - # Self produced units (these cannot be determined from commands) - elif garrison_mode == GenieGarrisonMode.SELF_PRODUCED: - if unit_line in garrison_line.creates: - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - - # Jedi/Sith inventories - elif garrison_mode == GenieGarrisonMode.MONK: - # Search for a pickup command - unit_commands = garrison_line.get_head_unit()["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - if type_id != 132: - continue - - unit_id = command["unit_id"].value - if unit_id == unit_line.get_head_unit_id(): - unit_line.garrison_locations.append(garrison_line) - garrison_line.garrison_entities.append(unit_line) - - @staticmethod - def link_repairables(full_data_set: GenieObjectContainer) -> None: - """ - Set units/buildings as repairable - - :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer - """ - villager_groups = full_data_set.villager_groups - - repair_lines = {} - repair_lines.update(full_data_set.unit_lines) - repair_lines.update(full_data_set.building_lines) - - repair_classes = [] - for villager in villager_groups.values(): - repair_unit = villager.get_units_with_command(106)[0] - unit_commands = repair_unit["unit_commands"].value - for command in unit_commands: - type_id = command["type"].value - - if type_id != 106: - continue - - class_id = command["class_id"].value - if class_id == -1: - # Buildings/Siege - repair_classes.append(10) - repair_classes.append(18) - repair_classes.append(32) - repair_classes.append(33) - repair_classes.append(34) - repair_classes.append(35) - repair_classes.append(36) - repair_classes.append(53) - - else: - repair_classes.append(class_id) - - for repair_line in repair_lines.values(): - if repair_line.get_class_id() in repair_classes: - repair_line.repairable = True + link_garrison = staticmethod(link_garrison) + link_repairables = staticmethod(link_repairables) From 849030d323cb065d91388cfcf977f563d5546dd0 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 18:32:13 +0200 Subject: [PATCH 151/163] convert: Refactor SWGBCCTechSubprocessor into separate files. --- .../conversion/swgbcc/CMakeLists.txt | 1 + .../conversion/swgbcc/tech/CMakeLists.txt | 6 + .../conversion/swgbcc/tech/__init__.py | 5 + .../swgbcc/tech/attribute_modify.py | 83 +++++++ .../conversion/swgbcc/tech/resource_modify.py | 57 +++++ .../conversion/swgbcc/tech/upgrade_funcs.py | 88 +++++++ .../conversion/swgbcc/tech_subprocessor.py | 223 ++---------------- 7 files changed, 257 insertions(+), 206 deletions(-) create mode 100644 openage/convert/processor/conversion/swgbcc/tech/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/tech/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/tech/attribute_modify.py create mode 100644 openage/convert/processor/conversion/swgbcc/tech/resource_modify.py create mode 100644 openage/convert/processor/conversion/swgbcc/tech/upgrade_funcs.py diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index b4e8e47bbe..f2a1398247 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -18,3 +18,4 @@ add_subdirectory(civ) add_subdirectory(main) add_subdirectory(nyan) add_subdirectory(pregen) +add_subdirectory(tech) diff --git a/openage/convert/processor/conversion/swgbcc/tech/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/tech/CMakeLists.txt new file mode 100644 index 0000000000..7e768537fe --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/tech/CMakeLists.txt @@ -0,0 +1,6 @@ +add_py_modules( + __init__.py + attribute_modify.py + resource_modify.py + upgrade_funcs.py +) diff --git a/openage/convert/processor/conversion/swgbcc/tech/__init__.py b/openage/convert/processor/conversion/swgbcc/tech/__init__.py new file mode 100644 index 0000000000..4585f5e957 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/tech/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create nyan patches for techs in SWGB. +""" diff --git a/openage/convert/processor/conversion/swgbcc/tech/attribute_modify.py b/openage/convert/processor/conversion/swgbcc/tech/attribute_modify.py new file mode 100644 index 0000000000..20fe364aa3 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/tech/attribute_modify.py @@ -0,0 +1,83 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying attributes of entities. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .upgrade_funcs import UPGRADE_ATTRIBUTE_FUNCS + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + from .....value_object.conversion.forward_ref import ForwardRef + + +def attribute_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying attributes of entities. + """ + patches = [] + dataset = converter_group.data + + effect_type = effect.get_type() + operator = None + if effect_type == 0: + operator = MemberOperator.ASSIGN + + elif effect_type == 4: + operator = MemberOperator.ADD + + elif effect_type == 5: + operator = MemberOperator.MULTIPLY + + else: + raise TypeError(f"Effect type {effect_type} is not a valid resource effect") + + unit_id = effect["attr_a"].value + class_id = effect["attr_b"].value + attribute_type = effect["attr_c"].value + value = effect["attr_d"].value + + if attribute_type == -1: + return patches + + affected_entities = [] + if unit_id != -1: + entity_lines = {} + entity_lines.update(dataset.unit_lines) + entity_lines.update(dataset.building_lines) + entity_lines.update(dataset.ambient_groups) + + for line in entity_lines.values(): + if line.contains_entity(unit_id): + affected_entities.append(line) + + elif attribute_type == 19: + if line.is_projectile_shooter() and line.has_projectile(unit_id): + affected_entities.append(line) + + elif class_id != -1: + entity_lines = {} + entity_lines.update(dataset.unit_lines) + entity_lines.update(dataset.building_lines) + entity_lines.update(dataset.ambient_groups) + + for line in entity_lines.values(): + if line.get_class_id() == class_id: + affected_entities.append(line) + + else: + return patches + + upgrade_func = UPGRADE_ATTRIBUTE_FUNCS[attribute_type] + for affected_entity in affected_entities: + patches.extend(upgrade_func(converter_group, affected_entity, value, operator, team)) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/tech/resource_modify.py b/openage/convert/processor/conversion/swgbcc/tech/resource_modify.py new file mode 100644 index 0000000000..25ce132573 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/tech/resource_modify.py @@ -0,0 +1,57 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates patches for modifying resources. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .upgrade_funcs import UPGRADE_RESOURCE_FUNCS + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_effect import GenieEffectObject + from .....value_object.conversion.forward_ref import ForwardRef + + +def resource_modify_effect( + converter_group: ConverterObjectGroup, + effect: GenieEffectObject, + team: bool = False +) -> list[ForwardRef]: + """ + Creates the patches for modifying resources. + """ + patches = [] + + effect_type = effect.get_type() + operator = None + if effect_type == 1: + mode = effect["attr_b"].value + + if mode == 0: + operator = MemberOperator.ASSIGN + + else: + operator = MemberOperator.ADD + + elif effect_type == 6: + operator = MemberOperator.MULTIPLY + + else: + raise TypeError(f"Effect type {effect_type} is not a valid resource effect") + + resource_id = effect["attr_a"].value + value = effect["attr_d"].value + + if resource_id in (-1, 6, 21): + # -1 = invalid ID + # 6 = set current age (unused) + # 21 = tech count (unused) + return patches + + upgrade_func = UPGRADE_RESOURCE_FUNCS[resource_id] + patches.extend(upgrade_func(converter_group, value, operator, team)) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/tech/upgrade_funcs.py b/openage/convert/processor/conversion/swgbcc/tech/upgrade_funcs.py new file mode 100644 index 0000000000..d71f76a068 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/tech/upgrade_funcs.py @@ -0,0 +1,88 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Mappings of SWGB upgrade IDs to their respective subprocessor functions. +""" +from ...aoc.upgrade_attribute_subprocessor import AoCUpgradeAttributeSubprocessor +from ...aoc.upgrade_resource_subprocessor import AoCUpgradeResourceSubprocessor +from ..upgrade_attribute_subprocessor import SWGBCCUpgradeAttributeSubprocessor +from ..upgrade_resource_subprocessor import SWGBCCUpgradeResourceSubprocessor + + +UPGRADE_ATTRIBUTE_FUNCS = { + 0: AoCUpgradeAttributeSubprocessor.hp_upgrade, + 1: AoCUpgradeAttributeSubprocessor.los_upgrade, + 2: AoCUpgradeAttributeSubprocessor.garrison_capacity_upgrade, + 3: AoCUpgradeAttributeSubprocessor.unit_size_x_upgrade, + 4: AoCUpgradeAttributeSubprocessor.unit_size_y_upgrade, + 5: AoCUpgradeAttributeSubprocessor.move_speed_upgrade, + 6: AoCUpgradeAttributeSubprocessor.rotation_speed_upgrade, + 8: AoCUpgradeAttributeSubprocessor.armor_upgrade, + 9: AoCUpgradeAttributeSubprocessor.attack_upgrade, + 10: AoCUpgradeAttributeSubprocessor.reload_time_upgrade, + 11: AoCUpgradeAttributeSubprocessor.accuracy_upgrade, + 12: AoCUpgradeAttributeSubprocessor.max_range_upgrade, + 13: AoCUpgradeAttributeSubprocessor.work_rate_upgrade, + 14: AoCUpgradeAttributeSubprocessor.carry_capacity_upgrade, + 16: AoCUpgradeAttributeSubprocessor.projectile_unit_upgrade, + 17: AoCUpgradeAttributeSubprocessor.graphics_angle_upgrade, + 18: AoCUpgradeAttributeSubprocessor.terrain_defense_upgrade, + 19: AoCUpgradeAttributeSubprocessor.ballistics_upgrade, + 20: AoCUpgradeAttributeSubprocessor.min_range_upgrade, + 21: AoCUpgradeAttributeSubprocessor.resource_storage_1_upgrade, + 22: AoCUpgradeAttributeSubprocessor.blast_radius_upgrade, + 23: AoCUpgradeAttributeSubprocessor.search_radius_upgrade, + 100: SWGBCCUpgradeAttributeSubprocessor.resource_cost_upgrade, + 101: AoCUpgradeAttributeSubprocessor.creation_time_upgrade, + 102: AoCUpgradeAttributeSubprocessor.min_projectiles_upgrade, + 103: AoCUpgradeAttributeSubprocessor.cost_food_upgrade, + 104: SWGBCCUpgradeAttributeSubprocessor.cost_carbon_upgrade, + 105: SWGBCCUpgradeAttributeSubprocessor.cost_nova_upgrade, + 106: SWGBCCUpgradeAttributeSubprocessor.cost_ore_upgrade, + 107: AoCUpgradeAttributeSubprocessor.max_projectiles_upgrade, + 108: AoCUpgradeAttributeSubprocessor.garrison_heal_upgrade, +} + +UPGRADE_RESOURCE_FUNCS = { + 4: AoCUpgradeResourceSubprocessor.starting_population_space_upgrade, + 5: SWGBCCUpgradeResourceSubprocessor.conversion_range_upgrade, + 10: SWGBCCUpgradeResourceSubprocessor.shield_recharge_rate_upgrade, + 23: SWGBCCUpgradeResourceSubprocessor.submarine_detect_upgrade, + 26: SWGBCCUpgradeResourceSubprocessor.shield_dropoff_time_upgrade, + 27: SWGBCCUpgradeResourceSubprocessor.monk_conversion_upgrade, + 28: SWGBCCUpgradeResourceSubprocessor.building_conversion_upgrade, + 31: SWGBCCUpgradeResourceSubprocessor.assault_mech_anti_air_upgrade, + 32: AoCUpgradeResourceSubprocessor.bonus_population_upgrade, + 33: SWGBCCUpgradeResourceSubprocessor.shield_power_core_upgrade, + 35: SWGBCCUpgradeResourceSubprocessor.faith_recharge_rate_upgrade, + 36: AoCUpgradeResourceSubprocessor.farm_food_upgrade, + 38: SWGBCCUpgradeResourceSubprocessor.shield_air_units_upgrade, + 46: AoCUpgradeResourceSubprocessor.tribute_inefficiency_upgrade, + 47: AoCUpgradeResourceSubprocessor.gather_gold_efficiency_upgrade, + 50: AoCUpgradeResourceSubprocessor.reveal_ally_upgrade, + 56: SWGBCCUpgradeResourceSubprocessor.cloak_upgrade, + 58: SWGBCCUpgradeResourceSubprocessor.detect_cloak_upgrade, + 77: AoCUpgradeResourceSubprocessor.conversion_resistance_upgrade, + 78: AoCUpgradeResourceSubprocessor.trade_penalty_upgrade, + 79: AoCUpgradeResourceSubprocessor.gather_stone_efficiency_upgrade, + 84: AoCUpgradeResourceSubprocessor.starting_villagers_upgrade, + 85: AoCUpgradeResourceSubprocessor.chinese_tech_discount_upgrade, + 87: SWGBCCUpgradeResourceSubprocessor.concentration_upgrade, + 89: AoCUpgradeResourceSubprocessor.heal_rate_upgrade, + 90: SWGBCCUpgradeResourceSubprocessor.heal_range_upgrade, + 91: AoCUpgradeResourceSubprocessor.starting_food_upgrade, + 92: AoCUpgradeResourceSubprocessor.starting_wood_upgrade, + 96: SWGBCCUpgradeResourceSubprocessor.berserk_heal_rate_upgrade, + 97: AoCUpgradeResourceSubprocessor.herding_dominance_upgrade, + 178: AoCUpgradeResourceSubprocessor.conversion_resistance_min_rounds_upgrade, + 179: AoCUpgradeResourceSubprocessor.conversion_resistance_max_rounds_upgrade, + 183: AoCUpgradeResourceSubprocessor.reveal_enemy_upgrade, + 189: AoCUpgradeResourceSubprocessor.gather_wood_efficiency_upgrade, + 190: AoCUpgradeResourceSubprocessor.gather_food_efficiency_upgrade, + 191: AoCUpgradeResourceSubprocessor.relic_gold_bonus_upgrade, + 192: AoCUpgradeResourceSubprocessor.heresy_upgrade, + 193: AoCUpgradeResourceSubprocessor.theocracy_upgrade, + 194: AoCUpgradeResourceSubprocessor.crenellations_upgrade, + 196: AoCUpgradeResourceSubprocessor.wonder_time_increase_upgrade, + 197: AoCUpgradeResourceSubprocessor.spies_discount_upgrade, +} diff --git a/openage/convert/processor/conversion/swgbcc/tech_subprocessor.py b/openage/convert/processor/conversion/swgbcc/tech_subprocessor.py index d6e7f25a2a..590c43175f 100644 --- a/openage/convert/processor/conversion/swgbcc/tech_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/tech_subprocessor.py @@ -1,9 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-branches -# -# TODO: -# pylint: disable=line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates patches for technologies. @@ -11,18 +6,16 @@ from __future__ import annotations import typing -from .....nyan.nyan_structs import MemberOperator from ....entity_object.conversion.aoc.genie_tech import CivTeamBonus, CivBonus from ..aoc.tech_subprocessor import AoCTechSubprocessor -from ..aoc.upgrade_attribute_subprocessor import AoCUpgradeAttributeSubprocessor -from ..aoc.upgrade_resource_subprocessor import AoCUpgradeResourceSubprocessor -from .upgrade_attribute_subprocessor import SWGBCCUpgradeAttributeSubprocessor -from .upgrade_resource_subprocessor import SWGBCCUpgradeResourceSubprocessor + +from .tech.attribute_modify import attribute_modify_effect +from .tech.resource_modify import resource_modify_effect +from .tech.upgrade_funcs import UPGRADE_ATTRIBUTE_FUNCS, UPGRADE_RESOURCE_FUNCS if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_effect import GenieEffectObject - from openage.convert.value_object.conversion.forward_ref import ForwardRef + from ....entity_object.conversion.converter_object import ConverterObjectGroup + from ....value_object.conversion.forward_ref import ForwardRef class SWGBCCTechSubprocessor: @@ -30,83 +23,8 @@ class SWGBCCTechSubprocessor: Creates raw API objects and patches for techs and civ setups in SWGB. """ - upgrade_attribute_funcs = { - 0: AoCUpgradeAttributeSubprocessor.hp_upgrade, - 1: AoCUpgradeAttributeSubprocessor.los_upgrade, - 2: AoCUpgradeAttributeSubprocessor.garrison_capacity_upgrade, - 3: AoCUpgradeAttributeSubprocessor.unit_size_x_upgrade, - 4: AoCUpgradeAttributeSubprocessor.unit_size_y_upgrade, - 5: AoCUpgradeAttributeSubprocessor.move_speed_upgrade, - 6: AoCUpgradeAttributeSubprocessor.rotation_speed_upgrade, - 8: AoCUpgradeAttributeSubprocessor.armor_upgrade, - 9: AoCUpgradeAttributeSubprocessor.attack_upgrade, - 10: AoCUpgradeAttributeSubprocessor.reload_time_upgrade, - 11: AoCUpgradeAttributeSubprocessor.accuracy_upgrade, - 12: AoCUpgradeAttributeSubprocessor.max_range_upgrade, - 13: AoCUpgradeAttributeSubprocessor.work_rate_upgrade, - 14: AoCUpgradeAttributeSubprocessor.carry_capacity_upgrade, - 16: AoCUpgradeAttributeSubprocessor.projectile_unit_upgrade, - 17: AoCUpgradeAttributeSubprocessor.graphics_angle_upgrade, - 18: AoCUpgradeAttributeSubprocessor.terrain_defense_upgrade, - 19: AoCUpgradeAttributeSubprocessor.ballistics_upgrade, - 20: AoCUpgradeAttributeSubprocessor.min_range_upgrade, - 21: AoCUpgradeAttributeSubprocessor.resource_storage_1_upgrade, - 22: AoCUpgradeAttributeSubprocessor.blast_radius_upgrade, - 23: AoCUpgradeAttributeSubprocessor.search_radius_upgrade, - 100: SWGBCCUpgradeAttributeSubprocessor.resource_cost_upgrade, - 101: AoCUpgradeAttributeSubprocessor.creation_time_upgrade, - 102: AoCUpgradeAttributeSubprocessor.min_projectiles_upgrade, - 103: AoCUpgradeAttributeSubprocessor.cost_food_upgrade, - 104: SWGBCCUpgradeAttributeSubprocessor.cost_carbon_upgrade, - 105: SWGBCCUpgradeAttributeSubprocessor.cost_nova_upgrade, - 106: SWGBCCUpgradeAttributeSubprocessor.cost_ore_upgrade, - 107: AoCUpgradeAttributeSubprocessor.max_projectiles_upgrade, - 108: AoCUpgradeAttributeSubprocessor.garrison_heal_upgrade, - } - - upgrade_resource_funcs = { - 4: AoCUpgradeResourceSubprocessor.starting_population_space_upgrade, - 5: SWGBCCUpgradeResourceSubprocessor.conversion_range_upgrade, - 10: SWGBCCUpgradeResourceSubprocessor.shield_recharge_rate_upgrade, - 23: SWGBCCUpgradeResourceSubprocessor.submarine_detect_upgrade, - 26: SWGBCCUpgradeResourceSubprocessor.shield_dropoff_time_upgrade, - 27: SWGBCCUpgradeResourceSubprocessor.monk_conversion_upgrade, - 28: SWGBCCUpgradeResourceSubprocessor.building_conversion_upgrade, - 31: SWGBCCUpgradeResourceSubprocessor.assault_mech_anti_air_upgrade, - 32: AoCUpgradeResourceSubprocessor.bonus_population_upgrade, - 33: SWGBCCUpgradeResourceSubprocessor.shield_power_core_upgrade, - 35: SWGBCCUpgradeResourceSubprocessor.faith_recharge_rate_upgrade, - 36: AoCUpgradeResourceSubprocessor.farm_food_upgrade, - 38: SWGBCCUpgradeResourceSubprocessor.shield_air_units_upgrade, - 46: AoCUpgradeResourceSubprocessor.tribute_inefficiency_upgrade, - 47: AoCUpgradeResourceSubprocessor.gather_gold_efficiency_upgrade, - 50: AoCUpgradeResourceSubprocessor.reveal_ally_upgrade, - 56: SWGBCCUpgradeResourceSubprocessor.cloak_upgrade, - 58: SWGBCCUpgradeResourceSubprocessor.detect_cloak_upgrade, - 77: AoCUpgradeResourceSubprocessor.conversion_resistance_upgrade, - 78: AoCUpgradeResourceSubprocessor.trade_penalty_upgrade, - 79: AoCUpgradeResourceSubprocessor.gather_stone_efficiency_upgrade, - 84: AoCUpgradeResourceSubprocessor.starting_villagers_upgrade, - 85: AoCUpgradeResourceSubprocessor.chinese_tech_discount_upgrade, - 87: SWGBCCUpgradeResourceSubprocessor.concentration_upgrade, - 89: AoCUpgradeResourceSubprocessor.heal_rate_upgrade, - 90: SWGBCCUpgradeResourceSubprocessor.heal_range_upgrade, - 91: AoCUpgradeResourceSubprocessor.starting_food_upgrade, - 92: AoCUpgradeResourceSubprocessor.starting_wood_upgrade, - 96: SWGBCCUpgradeResourceSubprocessor.berserk_heal_rate_upgrade, - 97: AoCUpgradeResourceSubprocessor.herding_dominance_upgrade, - 178: AoCUpgradeResourceSubprocessor.conversion_resistance_min_rounds_upgrade, - 179: AoCUpgradeResourceSubprocessor.conversion_resistance_max_rounds_upgrade, - 183: AoCUpgradeResourceSubprocessor.reveal_enemy_upgrade, - 189: AoCUpgradeResourceSubprocessor.gather_wood_efficiency_upgrade, - 190: AoCUpgradeResourceSubprocessor.gather_food_efficiency_upgrade, - 191: AoCUpgradeResourceSubprocessor.relic_gold_bonus_upgrade, - 192: AoCUpgradeResourceSubprocessor.heresy_upgrade, - 193: AoCUpgradeResourceSubprocessor.theocracy_upgrade, - 194: AoCUpgradeResourceSubprocessor.crenellations_upgrade, - 196: AoCUpgradeResourceSubprocessor.wonder_time_increase_upgrade, - 197: AoCUpgradeResourceSubprocessor.spies_discount_upgrade, - } + upgrade_attribute_funcs = UPGRADE_ATTRIBUTE_FUNCS + upgrade_resource_funcs = UPGRADE_RESOURCE_FUNCS @classmethod def get_patches(cls, converter_group: ConverterObjectGroup) -> list[ForwardRef]: @@ -144,14 +62,14 @@ def get_patches(cls, converter_group: ConverterObjectGroup) -> list[ForwardRef]: type_id -= 10 if type_id in (0, 4, 5): - patches.extend(cls.attribute_modify_effect(converter_group, - effect, - team=team_effect)) + patches.extend(attribute_modify_effect(converter_group, + effect, + team=team_effect)) elif type_id in (1, 6): - patches.extend(cls.resource_modify_effect(converter_group, - effect, - team=team_effect)) + patches.extend(resource_modify_effect(converter_group, + effect, + team=team_effect)) elif type_id == 2: # Enabling/disabling units: Handled in creatable conditions @@ -183,112 +101,5 @@ def get_patches(cls, converter_group: ConverterObjectGroup) -> list[ForwardRef]: return patches - @staticmethod - def attribute_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying attributes of entities. - """ - patches = [] - dataset = converter_group.data - - effect_type = effect.get_type() - operator = None - if effect_type == 0: - operator = MemberOperator.ASSIGN - - elif effect_type == 4: - operator = MemberOperator.ADD - - elif effect_type == 5: - operator = MemberOperator.MULTIPLY - - else: - raise TypeError(f"Effect type {effect_type} is not a valid resource effect") - - unit_id = effect["attr_a"].value - class_id = effect["attr_b"].value - attribute_type = effect["attr_c"].value - value = effect["attr_d"].value - - if attribute_type == -1: - return patches - - affected_entities = [] - if unit_id != -1: - entity_lines = {} - entity_lines.update(dataset.unit_lines) - entity_lines.update(dataset.building_lines) - entity_lines.update(dataset.ambient_groups) - - for line in entity_lines.values(): - if line.contains_entity(unit_id): - affected_entities.append(line) - - elif attribute_type == 19: - if line.is_projectile_shooter() and line.has_projectile(unit_id): - affected_entities.append(line) - - elif class_id != -1: - entity_lines = {} - entity_lines.update(dataset.unit_lines) - entity_lines.update(dataset.building_lines) - entity_lines.update(dataset.ambient_groups) - - for line in entity_lines.values(): - if line.get_class_id() == class_id: - affected_entities.append(line) - - else: - return patches - - upgrade_func = SWGBCCTechSubprocessor.upgrade_attribute_funcs[attribute_type] - for affected_entity in affected_entities: - patches.extend(upgrade_func(converter_group, affected_entity, value, operator, team)) - - return patches - - @staticmethod - def resource_modify_effect( - converter_group: ConverterObjectGroup, - effect: GenieEffectObject, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates the patches for modifying resources. - """ - patches = [] - - effect_type = effect.get_type() - operator = None - if effect_type == 1: - mode = effect["attr_b"].value - - if mode == 0: - operator = MemberOperator.ASSIGN - - else: - operator = MemberOperator.ADD - - elif effect_type == 6: - operator = MemberOperator.MULTIPLY - - else: - raise TypeError(f"Effect type {effect_type} is not a valid resource effect") - - resource_id = effect["attr_a"].value - value = effect["attr_d"].value - - if resource_id in (-1, 6, 21): - # -1 = invalid ID - # 6 = set current age (unused) - # 21 = tech count (unused) - return patches - - upgrade_func = SWGBCCTechSubprocessor.upgrade_resource_funcs[resource_id] - patches.extend(upgrade_func(converter_group, value, operator, team)) - - return patches + attribute_modify_effect = staticmethod(attribute_modify_effect) + resource_modify_effect = staticmethod(resource_modify_effect) From 3e0e6d7df4ada745c4a9695c8f38ffff1cd4e766 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 18:42:24 +0200 Subject: [PATCH 152/163] convert: Refactor SWBGCCUpgradeAttributeSubprocessor into separate files. --- .../conversion/swgbcc/CMakeLists.txt | 1 + .../swgbcc/upgrade_attribute/CMakeLists.txt | 7 + .../swgbcc/upgrade_attribute/__init__.py | 5 + .../swgbcc/upgrade_attribute/cost_carbon.py | 105 +++++ .../swgbcc/upgrade_attribute/cost_nova.py | 105 +++++ .../swgbcc/upgrade_attribute/cost_ore.py | 105 +++++ .../swgbcc/upgrade_attribute/resource_cost.py | 134 ++++++ .../swgbcc/upgrade_attribute_subprocessor.py | 428 +----------------- 8 files changed, 471 insertions(+), 419 deletions(-) create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_attribute/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_attribute/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_carbon.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_nova.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_ore.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_attribute/resource_cost.py diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index f2a1398247..ee0015739a 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -19,3 +19,4 @@ add_subdirectory(main) add_subdirectory(nyan) add_subdirectory(pregen) add_subdirectory(tech) +add_subdirectory(upgrade_attribute) diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_attribute/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/CMakeLists.txt new file mode 100644 index 0000000000..714c9b1ee6 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/CMakeLists.txt @@ -0,0 +1,7 @@ +add_py_modules( + __init__.py + cost_carbon.py + cost_nova.py + cost_ore.py + resource_cost.py +) diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_attribute/__init__.py b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/__init__.py new file mode 100644 index 0000000000..f8f539d812 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create upgrade patches for attributes in SWGB. +""" diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_carbon.py b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_carbon.py new file mode 100644 index 0000000000..992cc98a1a --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_carbon.py @@ -0,0 +1,105 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for crabon cost amounts in SWGB. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from ......nyan.nyan_structs import MemberOperator + + +def cost_carbon_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the carbon cost modify effect (ID: 104). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}Cost.CarbonAmount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}CarbonCostWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}CarbonCost" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_nova.py b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_nova.py new file mode 100644 index 0000000000..901d87971d --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_nova.py @@ -0,0 +1,105 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for nova cost amounts in SWGB. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from ......nyan.nyan_structs import MemberOperator + + +def cost_nova_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the nova cost modify effect (ID: 105). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}Cost.NovaAmount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}NovaCostWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}NovaCost" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_ore.py b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_ore.py new file mode 100644 index 0000000000..2acf19fd88 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/cost_ore.py @@ -0,0 +1,105 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for ore cost amounts in SWGB. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from ......nyan.nyan_structs import MemberOperator + + +def cost_ore_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the ore cost modify effect (ID: 106). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}Cost.OreAmount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}OreCostWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}OreCost" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_attribute/resource_cost.py b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/resource_cost.py new file mode 100644 index 0000000000..9a9fcbfb21 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_attribute/resource_cost.py @@ -0,0 +1,134 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for resource cost in SWGB. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup + from ......nyan.nyan_structs import MemberOperator + + +def resource_cost_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the resource modify effect (ID: 100). + + :param converter_group: Tech/Civ that gets the patch. + :param line: Unit/Building line that has the ability. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + head_unit = line.get_head_unit() + head_unit_id = line.get_head_unit_id() + dataset = line.data + + patches = [] + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[head_unit_id][0] + + for resource_amount in head_unit["resource_cost"].value: + resource_id = resource_amount["type_id"].value + resource_name = "" + if resource_id == -1: + # Not a valid resource + continue + + if resource_id == 0: + resource_name = "Food" + + elif resource_id == 1: + resource_name = "Carbon" + + elif resource_id == 2: + resource_name = "Ore" + + elif resource_id == 3: + resource_name = "Nova" + + else: + # Other resource ids are handled differently + continue + + # Skip resources that are only expected to be there + if not resource_amount["enabled"].value: + continue + + patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." + f"{game_entity_name}Cost." + f"{resource_name}Amount") + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}{resource_name}CostWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}{resource_name}Cost" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("amount", + value, + "engine.util.resource.ResourceAmount", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_attribute_subprocessor.py b/openage/convert/processor/conversion/swgbcc/upgrade_attribute_subprocessor.py index f65b0eb4b2..f71b2e7752 100644 --- a/openage/convert/processor/conversion/swgbcc/upgrade_attribute_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/upgrade_attribute_subprocessor.py @@ -1,429 +1,19 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. -# -# pylint: disable=too-many-locals,too-many-lines,too-many-statements,too-many-public-methods -# -# TODO: Remove when all methods are implemented -# pylint: disable=unused-argument,line-too-long +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ Creates upgrade patches for attribute modification effects in SWGB. """ -from __future__ import annotations -import typing - -from ....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.convert.entity_object.conversion.aoc.genie_unit import GenieGameEntityGroup - from openage.nyan.nyan_structs import MemberOperator +from .upgrade_attribute.cost_carbon import cost_carbon_upgrade +from .upgrade_attribute.cost_nova import cost_nova_upgrade +from .upgrade_attribute.cost_ore import cost_ore_upgrade +from .upgrade_attribute.resource_cost import resource_cost_upgrade class SWGBCCUpgradeAttributeSubprocessor: """ Creates raw API objects for attribute upgrade effects in SWGB. """ - - @staticmethod - def cost_carbon_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the carbon cost modify effect (ID: 104). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}Cost.CarbonAmount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}CarbonCostWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}CarbonCost" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def cost_nova_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the nova cost modify effect (ID: 105). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}Cost.NovaAmount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}NovaCostWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}NovaCost" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def cost_ore_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the ore cost modify effect (ID: 106). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}Cost.OreAmount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}OreCostWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}OreCost" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def resource_cost_upgrade( - converter_group: ConverterObjectGroup, - line: GenieGameEntityGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the resource modify effect (ID: 100). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param line: Unit/Building line that has the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - head_unit = line.get_head_unit() - head_unit_id = line.get_head_unit_id() - dataset = line.data - - patches = [] - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[head_unit_id][0] - - for resource_amount in head_unit["resource_cost"].value: - resource_id = resource_amount["type_id"].value - resource_name = "" - if resource_id == -1: - # Not a valid resource - continue - - if resource_id == 0: - resource_name = "Food" - - elif resource_id == 1: - resource_name = "Carbon" - - elif resource_id == 2: - resource_name = "Ore" - - elif resource_id == 3: - resource_name = "Nova" - - else: - # Other resource ids are handled differently - continue - - # Skip resources that are only expected to be there - if not resource_amount["enabled"].value: - continue - - patch_target_ref = (f"{game_entity_name}.CreatableGameEntity." - f"{game_entity_name}Cost." - f"{resource_name}Amount") - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}{resource_name}CostWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}{resource_name}Cost" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("amount", - value, - "engine.util.resource.ResourceAmount", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches + cost_carbon_upgrade = staticmethod(cost_carbon_upgrade) + cost_nova_upgrade = staticmethod(cost_nova_upgrade) + cost_ore_upgrade = staticmethod(cost_ore_upgrade) + resource_cost_upgrade = staticmethod(resource_cost_upgrade) From d03ab18976cc7f4beb77cabf8c7541bd304151f5 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 12 Jun 2025 19:06:26 +0200 Subject: [PATCH 153/163] convert: Refactor SWBGCCUpgradeResourceSubprocessor into separate files. --- .../conversion/swgbcc/CMakeLists.txt | 1 + .../swgbcc/upgrade_resource/CMakeLists.txt | 15 + .../swgbcc/upgrade_resource/__init__.py | 5 + .../upgrade_resource/assault_mech_anti_air.py | 33 + .../upgrade_resource/berserk_heal_rate.py | 104 +++ .../swgbcc/upgrade_resource/cloak.py | 54 ++ .../swgbcc/upgrade_resource/concentration.py | 33 + .../upgrade_resource/convert_building.py | 117 +++ .../swgbcc/upgrade_resource/convert_monk.py | 108 +++ .../swgbcc/upgrade_resource/convert_range.py | 33 + .../upgrade_resource/faith_recharge_rate.py | 107 +++ .../swgbcc/upgrade_resource/heal_range.py | 106 +++ .../swgbcc/upgrade_resource/shield.py | 96 +++ .../upgrade_resource/submarine_detect.py | 33 + .../swgbcc/upgrade_resource_subprocessor.py | 766 +----------------- 15 files changed, 872 insertions(+), 739 deletions(-) create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/CMakeLists.txt create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/__init__.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/assault_mech_anti_air.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/berserk_heal_rate.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/cloak.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/concentration.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_building.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_monk.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_range.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/faith_recharge_rate.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/heal_range.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/shield.py create mode 100644 openage/convert/processor/conversion/swgbcc/upgrade_resource/submarine_detect.py diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index ee0015739a..50ba2cd580 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -20,3 +20,4 @@ add_subdirectory(nyan) add_subdirectory(pregen) add_subdirectory(tech) add_subdirectory(upgrade_attribute) +add_subdirectory(upgrade_resource) diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/upgrade_resource/CMakeLists.txt new file mode 100644 index 0000000000..8e66b56886 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/CMakeLists.txt @@ -0,0 +1,15 @@ +add_py_modules( + __init__.py + assault_mech_anti_air.py + berserk_heal_rate.py + cloak.py + concentration.py + convert_building.py + convert_monk.py + convert_range.py + faith_recharge_rate.py + heal_range.py + __init__.py + shield.py + submarine_detect.py +) diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/__init__.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/__init__.py new file mode 100644 index 0000000000..4d4ec0fb7c --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Create upgrade patches for civ resources in SWBG. +""" diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/assault_mech_anti_air.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/assault_mech_anti_air.py new file mode 100644 index 0000000000..7827f43919 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/assault_mech_anti_air.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the assault mech anti air upgrade in SWBG. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def assault_mech_anti_air_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the assault mech anti air effect (ID: 31). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/berserk_heal_rate.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/berserk_heal_rate.py new file mode 100644 index 0000000000..cb13ae8068 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/berserk_heal_rate.py @@ -0,0 +1,104 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the berserk heal rate in SWBG. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from ......nyan.nyan_structs import MemberOperator + + +def berserk_heal_rate_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the berserk heal rate modify effect (ID: 96). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + berserk_id = 8 + dataset = converter_group.data + line = dataset.unit_lines[berserk_id] + + patches = [] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[berserk_id][0] + + patch_target_ref = f"{game_entity_name}.RegenerateHealth.HealthRate" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}HealthRegenerationWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}HealthRegeneration" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + # Regeneration is on a counter, so we have to invert the value + value = 1 / value + nyan_patch_raw_api_object.add_raw_patch_member("rate", + value, + "engine.util.attribute.AttributeRate", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( + ) + properties = { + dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/cloak.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/cloak.py new file mode 100644 index 0000000000..9e27c3a02c --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/cloak.py @@ -0,0 +1,54 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for cloak upgrades in SWBG. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def cloak_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the force cloak effect (ID: 56). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def detect_cloak_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the force detect cloak effect (ID: 58). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/concentration.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/concentration.py new file mode 100644 index 0000000000..7f22906b55 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/concentration.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the concentration upgrade in SWBG. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def concentration_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the concentration effect (ID: 87). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_building.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_building.py new file mode 100644 index 0000000000..3bc43a5e98 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_building.py @@ -0,0 +1,117 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for building conversion in SWBG. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + + +def convert_building_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the building conversion effect (ID: 28). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + force_ids = [115, 180] + dataset = converter_group.data + + patches = [] + + for force_id in force_ids: + line = dataset.unit_lines[force_id] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[force_id][0] + + patch_target_ref = f"{game_entity_name}.Convert" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Building conversion + + # Wrapper + wrapper_name = "EnableBuildingConversionWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = "EnableBuildingConversion" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + # New allowed types + allowed_types = [ + dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( + ) + ] + nyan_patch_raw_api_object.add_raw_patch_member("allowed_types", + allowed_types, + "engine.ability.type.ApplyDiscreteEffect", + MemberOperator.ADD) + + # Blacklisted buildings + tc_line = dataset.building_lines[109] + farm_line = dataset.building_lines[50] + temple_line = dataset.building_lines[104] + wonder_line = dataset.building_lines[276] + + blacklisted_forward_refs = [ForwardRef(tc_line, "CommandCenter"), + ForwardRef(farm_line, "Farm"), + ForwardRef(temple_line, "Temple"), + ForwardRef(wonder_line, "Monument"), + ] + nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", + blacklisted_forward_refs, + "engine.ability.type.ApplyDiscreteEffect", + 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) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_monk.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_monk.py new file mode 100644 index 0000000000..952c81e0c2 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_monk.py @@ -0,0 +1,108 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for monk conversion in SWBG. +""" +from __future__ import annotations +import typing + +from ......nyan.nyan_structs import MemberOperator +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + + +def convert_monk_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the monk conversion effect (ID: 27). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + force_ids = [115, 180] + dataset = converter_group.data + + patches = [] + + for force_id in force_ids: + line = dataset.unit_lines[force_id] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[force_id][0] + + patch_target_ref = f"{game_entity_name}.Convert" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Enable{game_entity_name}ConversionWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Enable{game_entity_name}Conversion" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + monk_forward_ref = ForwardRef(line, game_entity_name) + nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", + [monk_forward_ref], + "engine.ability.type.ApplyDiscreteEffect", + MemberOperator.SUBTRACT) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects[ + "util.patch.property.types.Team" + ].get_nyan_object() + properties = { + dataset.nyan_api_objects[ + "engine.util.patch.property.type.Diplomatic" + ]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_range.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_range.py new file mode 100644 index 0000000000..1dcc55f30f --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/convert_range.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for conversion range in SWBG. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def convert_range_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the conversion range modify effect (ID: 5). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/faith_recharge_rate.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/faith_recharge_rate.py new file mode 100644 index 0000000000..a2a347f101 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/faith_recharge_rate.py @@ -0,0 +1,107 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for the faith recharge rate in SWBG. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from ......nyan.nyan_structs import MemberOperator + + +def faith_recharge_rate_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the faith_recharge_rate modify effect (ID: 35). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + force_ids = [115, 180] + dataset = converter_group.data + + patches = [] + + for force_id in force_ids: + line = dataset.unit_lines[force_id] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[force_id][0] + + patch_target_ref = f"{game_entity_name}.RegenerateFaith.FaithRate" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}FaithRegenerationWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}FaithRegeneration" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("rate", + value, + "engine.util.attribute.AttributeRate", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects[ + "util.patch.property.types.Team" + ].get_nyan_object() + properties = { + dataset.nyan_api_objects[ + "engine.util.patch.property.type.Diplomatic" + ]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/heal_range.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/heal_range.py new file mode 100644 index 0000000000..eb98aacc8c --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/heal_range.py @@ -0,0 +1,106 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for heal range in SWBG. +""" +from __future__ import annotations +import typing + +from .....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup +from .....entity_object.conversion.converter_object import RawAPIObject +from .....service.conversion import internal_name_lookups +from .....value_object.conversion.forward_ref import ForwardRef + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from ......nyan.nyan_structs import MemberOperator + + +def heal_range_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the heal range modify effect (ID: 90). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + medic_id = 939 + dataset = converter_group.data + + patches = [] + + line = dataset.unit_lines[medic_id] + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + obj_id = converter_group.get_id() + if isinstance(converter_group, GenieTechEffectBundleGroup): + tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) + obj_name = tech_lookup_dict[obj_id][0] + + else: + civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) + obj_name = civ_lookup_dict[obj_id][0] + + game_entity_name = name_lookup_dict[medic_id][0] + + patch_target_ref = f"{game_entity_name}.Heal.Ranged" + patch_target_forward_ref = ForwardRef(line, patch_target_ref) + + # Wrapper + wrapper_name = f"Change{game_entity_name}HealRangeWrapper" + wrapper_ref = f"{obj_name}.{wrapper_name}" + wrapper_location = ForwardRef(converter_group, obj_name) + wrapper_raw_api_object = RawAPIObject(wrapper_ref, + wrapper_name, + dataset.nyan_api_objects, + wrapper_location) + wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") + + # Nyan patch + nyan_patch_name = f"Change{game_entity_name}HealRange" + nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) + + nyan_patch_raw_api_object.add_raw_patch_member("max_range", + value, + "engine.ability.property.type.Ranged", + operator) + + patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) + wrapper_raw_api_object.add_raw_member("patch", + patch_forward_ref, + "engine.util.patch.Patch") + + if team: + team_property = dataset.pregen_nyan_objects[ + "util.patch.property.types.Team" + ].get_nyan_object() + properties = { + dataset.nyan_api_objects[ + "engine.util.patch.property.type.Diplomatic" + ]: team_property + } + wrapper_raw_api_object.add_raw_member("properties", + properties, + "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) + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/shield.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/shield.py new file mode 100644 index 0000000000..9070ef2871 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/shield.py @@ -0,0 +1,96 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for shield upgrades in SWBG. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def shield_air_units_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the shield bomber/fighter effect (ID: 38). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def shield_dropoff_time_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the shield dropoff time modify effect (ID: 26). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def shield_power_core_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the shield power core effect (ID: 33). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches + + +def shield_recharge_rate_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the shield recharge rate modify effect (ID: 10). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # TODO: Implement + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource/submarine_detect.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource/submarine_detect.py new file mode 100644 index 0000000000..ef0710bf7e --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource/submarine_detect.py @@ -0,0 +1,33 @@ +# Copyright 2025-2025 the openage authors. See copying.md for legal info. + +""" +Creates upgrade patches for submarine detection in SWBG. +""" +from __future__ import annotations +import typing + +if typing.TYPE_CHECKING: + from .....entity_object.conversion.converter_object import ConverterObjectGroup + from .....value_object.conversion.forward_ref import ForwardRef + from ......nyan.nyan_structs import MemberOperator + + +def submarine_detect_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Any, + operator: MemberOperator, + team: bool = False +) -> list[ForwardRef]: + """ + Creates a patch for the submarine detect effect (ID: 23). + + :param converter_group: Tech/Civ that gets the patch. + :param value: Value used for patching the member. + :param operator: Operator used for patching the member. + :returns: The forward references for the generated patches. + """ + patches = [] + + # Unused + + return patches diff --git a/openage/convert/processor/conversion/swgbcc/upgrade_resource_subprocessor.py b/openage/convert/processor/conversion/swgbcc/upgrade_resource_subprocessor.py index 55c141a70a..ad60705ce7 100644 --- a/openage/convert/processor/conversion/swgbcc/upgrade_resource_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/upgrade_resource_subprocessor.py @@ -1,25 +1,20 @@ # 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 -# -# TODO: Remove when all methods are implemented -# pylint: disable=unused-argument,line-too-long """ Creates upgrade patches for resource modification effects in SWGB. """ -from __future__ import annotations -import typing - -from .....nyan.nyan_structs import MemberOperator -from ....entity_object.conversion.aoc.genie_tech import GenieTechEffectBundleGroup -from ....entity_object.conversion.converter_object import RawAPIObject -from ....service.conversion import internal_name_lookups -from ....value_object.conversion.forward_ref import ForwardRef - -if typing.TYPE_CHECKING: - from openage.convert.entity_object.conversion.converter_object import ConverterObjectGroup - from openage.nyan.nyan_structs import MemberOperator +from .upgrade_resource.assault_mech_anti_air import assault_mech_anti_air_upgrade +from .upgrade_resource.berserk_heal_rate import berserk_heal_rate_upgrade +from .upgrade_resource.cloak import cloak_upgrade, detect_cloak_upgrade +from .upgrade_resource.concentration import concentration_upgrade +from .upgrade_resource.convert_building import convert_building_upgrade +from .upgrade_resource.convert_monk import convert_monk_upgrade +from .upgrade_resource.faith_recharge_rate import faith_recharge_rate_upgrade +from .upgrade_resource.heal_range import heal_range_upgrade +from .upgrade_resource.shield import shield_air_units_upgrade, shield_dropoff_time_upgrade, \ + shield_power_core_upgrade, shield_recharge_rate_upgrade +from .upgrade_resource.submarine_detect import submarine_detect_upgrade +from .upgrade_resource.convert_range import convert_range_upgrade class SWGBCCUpgradeResourceSubprocessor: @@ -27,725 +22,18 @@ class SWGBCCUpgradeResourceSubprocessor: Creates raw API objects for resource upgrade effects in SWGB. """ - @staticmethod - def assault_mech_anti_air_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the assault mech anti air effect (ID: 31). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def berserk_heal_rate_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the berserk heal rate modify effect (ID: 96). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - berserk_id = 8 - dataset = converter_group.data - line = dataset.unit_lines[berserk_id] - - patches = [] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[berserk_id][0] - - patch_target_ref = f"{game_entity_name}.RegenerateHealth.HealthRate" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}HealthRegenerationWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}HealthRegeneration" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - # Regeneration is on a counter, so we have to invert the value - value = 1 / value - nyan_patch_raw_api_object.add_raw_patch_member("rate", - value, - "engine.util.attribute.AttributeRate", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def building_conversion_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the building conversion effect (ID: 28). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - force_ids = [115, 180] - dataset = converter_group.data - - patches = [] - - for force_id in force_ids: - line = dataset.unit_lines[force_id] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[force_id][0] - - patch_target_ref = f"{game_entity_name}.Convert" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Building conversion - - # Wrapper - wrapper_name = "EnableBuildingConversionWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = "EnableBuildingConversion" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - # New allowed types - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object( - ) - ] - nyan_patch_raw_api_object.add_raw_patch_member("allowed_types", - allowed_types, - "engine.ability.type.ApplyDiscreteEffect", - MemberOperator.ADD) - - # Blacklisted buildings - tc_line = dataset.building_lines[109] - farm_line = dataset.building_lines[50] - temple_line = dataset.building_lines[104] - wonder_line = dataset.building_lines[276] - - blacklisted_forward_refs = [ForwardRef(tc_line, "CommandCenter"), - ForwardRef(farm_line, "Farm"), - ForwardRef(temple_line, "Temple"), - ForwardRef(wonder_line, "Monument"), - ] - nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", - blacklisted_forward_refs, - "engine.ability.type.ApplyDiscreteEffect", - 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) - - return patches - - @staticmethod - def cloak_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the force cloak effect (ID: 56). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def concentration_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the concentration effect (ID: 87). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def conversion_range_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the conversion range modify effect (ID: 5). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def detect_cloak_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the force detect cloak effect (ID: 58). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def faith_recharge_rate_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the faith_recharge_rate modify effect (ID: 35). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - force_ids = [115, 180] - dataset = converter_group.data - - patches = [] - - for force_id in force_ids: - line = dataset.unit_lines[force_id] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[force_id][0] - - patch_target_ref = f"{game_entity_name}.RegenerateFaith.FaithRate" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}FaithRegenerationWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}FaithRegeneration" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("rate", - value, - "engine.util.attribute.AttributeRate", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def heal_range_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the heal range modify effect (ID: 90). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - medic_id = 939 - dataset = converter_group.data - - patches = [] - - line = dataset.unit_lines[medic_id] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[medic_id][0] - - patch_target_ref = f"{game_entity_name}.Heal.Ranged" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Change{game_entity_name}HealRangeWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Change{game_entity_name}HealRange" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - nyan_patch_raw_api_object.add_raw_patch_member("max_range", - value, - "engine.ability.property.type.Ranged", - operator) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def monk_conversion_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the monk conversion effect (ID: 27). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - force_ids = [115, 180] - dataset = converter_group.data - - patches = [] - - for force_id in force_ids: - line = dataset.unit_lines[force_id] - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - obj_id = converter_group.get_id() - if isinstance(converter_group, GenieTechEffectBundleGroup): - tech_lookup_dict = internal_name_lookups.get_tech_lookups(dataset.game_version) - obj_name = tech_lookup_dict[obj_id][0] - - else: - civ_lookup_dict = internal_name_lookups.get_civ_lookups(dataset.game_version) - obj_name = civ_lookup_dict[obj_id][0] - - game_entity_name = name_lookup_dict[force_id][0] - - patch_target_ref = f"{game_entity_name}.Convert" - patch_target_forward_ref = ForwardRef(line, patch_target_ref) - - # Wrapper - wrapper_name = f"Enable{game_entity_name}ConversionWrapper" - wrapper_ref = f"{obj_name}.{wrapper_name}" - wrapper_location = ForwardRef(converter_group, obj_name) - wrapper_raw_api_object = RawAPIObject(wrapper_ref, - wrapper_name, - dataset.nyan_api_objects, - wrapper_location) - wrapper_raw_api_object.add_raw_parent("engine.util.patch.Patch") - - # Nyan patch - nyan_patch_name = f"Enable{game_entity_name}Conversion" - nyan_patch_ref = f"{obj_name}.{wrapper_name}.{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) - - monk_forward_ref = ForwardRef(line, game_entity_name) - nyan_patch_raw_api_object.add_raw_patch_member("blacklisted_entities", - [monk_forward_ref], - "engine.ability.type.ApplyDiscreteEffect", - MemberOperator.SUBTRACT) - - patch_forward_ref = ForwardRef(converter_group, nyan_patch_ref) - wrapper_raw_api_object.add_raw_member("patch", - patch_forward_ref, - "engine.util.patch.Patch") - - if team: - team_property = dataset.pregen_nyan_objects["util.patch.property.types.Team"].get_nyan_object( - ) - properties = { - dataset.nyan_api_objects["engine.util.patch.property.type.Diplomatic"]: team_property - } - wrapper_raw_api_object.add_raw_member("properties", - properties, - "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) - - return patches - - @staticmethod - def shield_air_units_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the shield bomber/fighter effect (ID: 38). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def shield_dropoff_time_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the shield dropoff time modify effect (ID: 26). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def shield_power_core_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the shield power core effect (ID: 33). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def shield_recharge_rate_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Union[int, float], - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the shield recharge rate modify effect (ID: 10). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: int, float - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # TODO: Implement - - return patches - - @staticmethod - def submarine_detect_upgrade( - converter_group: ConverterObjectGroup, - value: typing.Any, - operator: MemberOperator, - team: bool = False - ) -> list[ForwardRef]: - """ - Creates a patch for the submarine detect effect (ID: 23). - - :param converter_group: Tech/Civ that gets the patch. - :type converter_group: ...dataformat.converter_object.ConverterObjectGroup - :param value: Value used for patching the member. - :type value: Any - :param operator: Operator used for patching the member. - :type operator: MemberOperator - :returns: The forward references for the generated patches. - :rtype: list - """ - patches = [] - - # Unused - - return patches + assault_mech_anti_air_upgrade = staticmethod(assault_mech_anti_air_upgrade) + berserk_heal_rate_upgrade = staticmethod(berserk_heal_rate_upgrade) + cloak_upgrade = staticmethod(cloak_upgrade) + detect_cloak_upgrade = staticmethod(detect_cloak_upgrade) + concentration_upgrade = staticmethod(concentration_upgrade) + building_conversion_upgrade = staticmethod(convert_building_upgrade) + monk_conversion_upgrade = staticmethod(convert_monk_upgrade) + faith_recharge_rate_upgrade = staticmethod(faith_recharge_rate_upgrade) + heal_range_upgrade = staticmethod(heal_range_upgrade) + shield_air_units_upgrade = staticmethod(shield_air_units_upgrade) + shield_dropoff_time_upgrade = staticmethod(shield_dropoff_time_upgrade) + shield_power_core_upgrade = staticmethod(shield_power_core_upgrade) + shield_recharge_rate_upgrade = staticmethod(shield_recharge_rate_upgrade) + submarine_detect_upgrade = staticmethod(submarine_detect_upgrade) + conversion_range_upgrade = staticmethod(convert_range_upgrade) From ee4b510737ac4740fca5acb1972300bf387d941b Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 22 Jun 2025 16:07:18 +0200 Subject: [PATCH 154/163] convert: Remove unsupported file xrefs from EnumLookupMember. --- .../convert/value_object/read/read_members.py | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/openage/convert/value_object/read/read_members.py b/openage/convert/value_object/read/read_members.py index 6e07e471b2..c4babc319d 100644 --- a/openage/convert/value_object/read/read_members.py +++ b/openage/convert/value_object/read/read_members.py @@ -1,4 +1,4 @@ -# Copyright 2014-2023 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. # # TODO pylint: disable=C,R,abstract-method @@ -265,14 +265,12 @@ class EnumMember(RefMember): def __init__( self, type_name: str, - values: dict[typing.Any, typing.Any], - file_name: str = None + values: list[str] ): - super().__init__(type_name, file_name) + super().__init__(type_name, None) # external xrefs unsupported self.values = values - self.resolved = True # TODO, xrefs not supported yet. - def validate_value(self, value: typing.Any) -> bool: + def validate_value(self, value: str) -> bool: return value in self.values def __repr__(self): @@ -281,36 +279,36 @@ def __repr__(self): class EnumLookupMember(EnumMember): """ - enum definition, does lookup of raw datfile data => enum value + enum definition, does lookup of raw datfile data => human-readable enum value """ def __init__( self, type_name: str, - lookup_dict: dict[typing.Any, typing.Any], - raw_type: str, - file_name: str = None + lookup_dict: dict[int, str], + raw_type: str ): super().__init__( type_name, - [v for k, v in sorted(lookup_dict.items())], - file_name + sorted(list(lookup_dict.values())) ) self.lookup_dict = lookup_dict self.raw_type = raw_type - def entry_hook(self, data: typing.Any) -> typing.Any: + def entry_hook(self, data: int) -> str: """ perform lookup of raw data -> enum member name """ - try: return self.lookup_dict[data] + except KeyError: try: h = f" = {hex(data)}" + except TypeError: h = "" + raise KeyError("failed to find %s%s in lookup dict %s!" % (str(data), h, self.type_name)) from None From d0d2f5917dfedba07bf3b010b82784ad6144a57d Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 22 Jun 2025 16:28:41 +0200 Subject: [PATCH 155/163] convert: Handle unknown enum values in dat file more gracefully. --- .../convert/value_object/read/read_members.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openage/convert/value_object/read/read_members.py b/openage/convert/value_object/read/read_members.py index c4babc319d..e2a681bcca 100644 --- a/openage/convert/value_object/read/read_members.py +++ b/openage/convert/value_object/read/read_members.py @@ -7,6 +7,8 @@ from enum import Enum +from ....log import warn + if typing.TYPE_CHECKING: from openage.convert.value_object.read.genie_structure import GenieStructure @@ -286,7 +288,8 @@ def __init__( self, type_name: str, lookup_dict: dict[int, str], - raw_type: str + raw_type: str, + unknown_lookup_prefix: str = None, ): super().__init__( type_name, @@ -295,14 +298,24 @@ def __init__( self.lookup_dict = lookup_dict self.raw_type = raw_type + self.unknown_lookup_prefix = unknown_lookup_prefix + def entry_hook(self, data: int) -> str: """ perform lookup of raw data -> enum member name + + Throws an error if the lookup fails and no unknown lookup prefix is defined. """ - try: + if data in self.lookup_dict: return self.lookup_dict[data] - except KeyError: + elif self.unknown_lookup_prefix is not None: + unknown_string = f"{self.unknown_lookup_prefix}_{data:#X}" + warn("Could not find enum string for value %s, using '%s'", + str(data), unknown_string) + return unknown_string + + else: try: h = f" = {hex(data)}" From fafb7e6b86181777c3b5b0ce075c5d50842ae7d9 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 22 Jun 2025 18:47:36 +0200 Subject: [PATCH 156/163] convert: Add support for newest DE2 version .dat file. --- .../value_object/read/media/datfile/civ.py | 9 +- .../read/media/datfile/graphic.py | 5 +- .../read/media/datfile/lookup_dicts.py | 17 +++- .../read/media/datfile/research.py | 5 +- .../value_object/read/media/datfile/tech.py | 8 +- .../value_object/read/media/datfile/unit.py | 94 ++++++++++++++----- 6 files changed, 98 insertions(+), 40 deletions(-) diff --git a/openage/convert/value_object/read/media/datfile/civ.py b/openage/convert/value_object/read/media/datfile/civ.py index fa460cc633..b2d6e9565e 100644 --- a/openage/convert/value_object/read/media/datfile/civ.py +++ b/openage/convert/value_object/read/media/datfile/civ.py @@ -1,4 +1,4 @@ -# Copyright 2013-2023 the openage authors. See copying.md for legal info. +# Copyright 2013-2025 the openage authors. See copying.md for legal info. # TODO pylint: disable=C,R from __future__ import annotations @@ -71,9 +71,10 @@ def get_data_format_members( (READ_GEN, "units", StorageType.ARRAY_CONTAINER, MultisubtypeMember( type_name = "unit_types", subtype_definition = (READ_GEN, "unit_type", StorageType.ID_MEMBER, EnumLookupMember( - type_name = "unit_type_id", - lookup_dict = unit.unit_type_lookup, - raw_type = "int8_t", + raw_type = "int8_t", + type_name = "unit_type_id", + lookup_dict = unit.unit_type_lookup, + unknown_lookup_prefix = "UNKNOWN_UNIT_TYPE" )), class_lookup = unit.unit_type_class_lookup, length = "unit_count", diff --git a/openage/convert/value_object/read/media/datfile/graphic.py b/openage/convert/value_object/read/media/datfile/graphic.py index 30bfc9d97c..523d1b78c3 100644 --- a/openage/convert/value_object/read/media/datfile/graphic.py +++ b/openage/convert/value_object/read/media/datfile/graphic.py @@ -1,4 +1,4 @@ -# Copyright 2013-2023 the openage authors. See copying.md for legal info. +# Copyright 2013-2025 the openage authors. See copying.md for legal info. # TODO pylint: disable=C,R from __future__ import annotations @@ -173,7 +173,8 @@ def get_data_format_members( (READ_GEN, "layer", StorageType.ID_MEMBER, EnumLookupMember( # originally 40 layers, higher -> drawn on top raw_type = "int8_t", # -> same layer -> order according to map position. type_name = "graphics_layer", - lookup_dict = GRAPHICS_LAYER + lookup_dict = GRAPHICS_LAYER, + unknown_lookup_prefix="UNKNOWN_GRAPHICS_LAYER" )), (SKIP, "player_color_force_id", StorageType.ID_MEMBER, "int8_t"), # force given player color diff --git a/openage/convert/value_object/read/media/datfile/lookup_dicts.py b/openage/convert/value_object/read/media/datfile/lookup_dicts.py index dfd2206612..32271b7f49 100644 --- a/openage/convert/value_object/read/media/datfile/lookup_dicts.py +++ b/openage/convert/value_object/read/media/datfile/lookup_dicts.py @@ -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. """ Lookup dicts for the EnumLookupMember instances. @@ -341,9 +341,13 @@ 103: "TECH_TIME_MODIFY", # a == research_id, if c == 0: d==absval else d==relval # unknown; used in DE2 BfG - 199: "UNKNOWN", - 200: "UNKNOWN", - 201: "UNKNOWN", + # used in DE2: BfG and specific to the unique town center upgrades + # same as 0 + 200: "DE2_TOWN_CENTER_ATTRIBUTE_ABSSET", + # same as 4 + 201: "DE2_TOWN_CENTER_ATTRIBUTE_RELSET", + # same as 5 + 202: "DE2_TOWN_CENTER_ATTRIBUTE_MUL", # attribute_id: # 0: hit points @@ -441,6 +445,8 @@ 153: "RESOURCE_FOLLOW", 154: "LOOT", # Chieftains tech; looting on killing villagers, monks, trade carts 155: "BOOST_MOVE_AND_ATTACK", + 157: "DE2_CHARGE", + 158: "DE2_JIAN_SHIELD", # Jian swordsman unique ability; when HP is low: change stats 768: "UNKNOWN_768", 1024: "UNKNOWN_1024", } @@ -635,6 +641,7 @@ 0x1E: "DE2_UNKNOWN", 0x1F: "SWGB_WATER2", 0x20: "SWGB_ROCK4", + 0x31: "DE2_ROCKET_CART_PROJECTILE", # probably created for projectiles to decay properly } BLAST_DEFENSE_TYPES = { @@ -699,6 +706,7 @@ 3: "BERSERK", 5: "UNIT", 10: "MOUNTAIN", # mountain (matches occlusion_mask) + 13: "DE2_PASTURE", # pasture construction site (from DE2: TTK) } SELECTION_EFFECTS = { @@ -736,6 +744,7 @@ 3: "TARGET_ONLY", 6: "UNKNOWN_6", 10: "UNKNOWN_10", + 11: "DE2_GRENADIER", 18: "UNKNOWN_18", 34: "UNKNOWN_34", 66: "UNKNOWN_66", diff --git a/openage/convert/value_object/read/media/datfile/research.py b/openage/convert/value_object/read/media/datfile/research.py index c1169dfbed..d205fad164 100644 --- a/openage/convert/value_object/read/media/datfile/research.py +++ b/openage/convert/value_object/read/media/datfile/research.py @@ -1,4 +1,4 @@ -# Copyright 2013-2023 the openage authors. See copying.md for legal info. +# Copyright 2013-2025 the openage authors. See copying.md for legal info. # TODO pylint: disable=C,R from __future__ import annotations @@ -33,7 +33,8 @@ def get_data_format_members( (READ_GEN, "type_id", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int16_t", type_name="resource_types", - lookup_dict=RESOURCE_TYPES + lookup_dict=RESOURCE_TYPES, + unknown_lookup_prefix="UNKNOWN_RESOURCE_TYPE" )), # see unit/resource_cost (READ_GEN, "amount", StorageType.INT_MEMBER, "int16_t"), (READ_GEN, "enabled", StorageType.BOOLEAN_MEMBER, "int8_t"), diff --git a/openage/convert/value_object/read/media/datfile/tech.py b/openage/convert/value_object/read/media/datfile/tech.py index 54c350dadd..fb6119565e 100644 --- a/openage/convert/value_object/read/media/datfile/tech.py +++ b/openage/convert/value_object/read/media/datfile/tech.py @@ -1,4 +1,4 @@ -# Copyright 2013-2024 the openage authors. See copying.md for legal info. +# Copyright 2013-2025 the openage authors. See copying.md for legal info. # TODO pylint: disable=C,R from __future__ import annotations @@ -33,7 +33,8 @@ def get_data_format_members( (READ_GEN, "type_id", StorageType.ID_MEMBER, EnumLookupMember( raw_type="uint8_t", type_name="effect_apply_type", - lookup_dict=EFFECT_APPLY_TYPE + lookup_dict=EFFECT_APPLY_TYPE, + unknown_lookup_prefix="UNKNOWN_EFFECT_APPLY_TYPE" )), (READ_GEN, "attr_a", StorageType.INT_MEMBER, "int16_t"), (READ_GEN, "attr_b", StorageType.INT_MEMBER, "int16_t"), @@ -93,7 +94,8 @@ def get_data_format_members( (READ_GEN, "other_connection", StorageType.ID_MEMBER, EnumLookupMember( # mode for unit_or_research0 raw_type="int32_t", type_name="connection_mode", - lookup_dict=CONNECTION_MODE + lookup_dict=CONNECTION_MODE, + unknown_lookup_prefix="UNKNOWN_CONNECTION_MODE" )), ] diff --git a/openage/convert/value_object/read/media/datfile/unit.py b/openage/convert/value_object/read/media/datfile/unit.py index ae0e7c5f99..c77cd78ac2 100644 --- a/openage/convert/value_object/read/media/datfile/unit.py +++ b/openage/convert/value_object/read/media/datfile/unit.py @@ -1,4 +1,4 @@ -# Copyright 2013-2024 the openage authors. See copying.md for legal info. +# Copyright 2013-2025 the openage authors. See copying.md for legal info. # TODO pylint: disable=C,R,too-many-lines from __future__ import annotations @@ -46,7 +46,8 @@ def get_data_format_members( (READ_GEN, "type", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int16_t", type_name="command_ability", - lookup_dict=COMMAND_ABILITY + lookup_dict=COMMAND_ABILITY, + unknown_lookup_prefix="UNKNOWN_COMBAT_ABILITY" )), (READ_GEN, "class_id", StorageType.ID_MEMBER, "int16_t"), (READ_GEN, "unit_id", StorageType.ID_MEMBER, "int16_t"), @@ -69,7 +70,8 @@ def get_data_format_members( # what can be selected as a target for the unit command? raw_type="int8_t", type_name="selection_type", - lookup_dict=OWNER_TYPE + lookup_dict=OWNER_TYPE, + unknown_lookup_prefix="UNKNOWN_OWNER_TYPE" )), # checks if the targeted unit has > 0 resources (SKIP, "carry_check", StorageType.BOOLEAN_MEMBER, "int8_t"), @@ -161,7 +163,8 @@ def get_data_format_members( (SKIP, "used_mode", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int8_t", type_name="resource_handling", - lookup_dict=RESOURCE_HANDLING + lookup_dict=RESOURCE_HANDLING, + unknown_lookup_prefix="UNKNOWN_RESOURCE_HANDLING" )), ] @@ -187,7 +190,8 @@ def get_data_format_members( (SKIP, "apply_mode", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int8_t", type_name="damage_draw_type", - lookup_dict=DAMAGE_DRAW_TYPE + lookup_dict=DAMAGE_DRAW_TYPE, + unknown_lookup_prefix="UNKNOWN_DAMAGE_DRAW_TYPE" )), ] @@ -209,7 +213,8 @@ def get_data_format_members( (READ_GEN, "type_id", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int16_t", type_name="hit_class", - lookup_dict=ARMOR_CLASS + lookup_dict=ARMOR_CLASS, + unknown_lookup_prefix="UNKNOWN_ARMOR_CLASS" )), (READ_GEN, "amount", StorageType.INT_MEMBER, "int16_t"), ] @@ -232,7 +237,8 @@ def get_data_format_members( (READ_GEN, "type_id", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int16_t", type_name="resource_types", - lookup_dict=RESOURCE_TYPES + lookup_dict=RESOURCE_TYPES, + unknown_lookup_prefix="UNKNOWN_RESOURCE_TYPE" )), (READ_GEN, "amount", StorageType.INT_MEMBER, "int16_t"), (READ_GEN, "enabled", StorageType.BOOLEAN_MEMBER, "int16_t"), @@ -301,7 +307,8 @@ def get_data_format_members( (READ_GEN, "unit_class", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int16_t", type_name="unit_classes", - lookup_dict=UNIT_CLASSES + lookup_dict=UNIT_CLASSES, + unknown_lookup_prefix="UNKNOWN_UNIT_CLASS" )), (READ_GEN, "idle_graphic0", StorageType.ID_MEMBER, "int16_t"), ]) @@ -372,17 +379,20 @@ def get_data_format_members( (READ_GEN, "elevation_mode", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int8_t", type_name="elevation_modes", - lookup_dict=ELEVATION_MODES + lookup_dict=ELEVATION_MODES, + unknown_lookup_prefix="UNKNOWN_ELEVATION_MODE" )), (READ_GEN, "visible_in_fog", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int8_t", type_name="fog_visibility", - lookup_dict=FOG_VISIBILITY + lookup_dict=FOG_VISIBILITY, + unknown_lookup_prefix="UNKNOWN_FOG_VISIBILITY" )), (READ_GEN, "terrain_restriction", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int16_t", # determines on what type of ground the unit can be placed/walk type_name="ground_type", # is actually the id of the terrain_restriction entry! - lookup_dict=TERRAIN_RESTRICTIONS + lookup_dict=TERRAIN_RESTRICTIONS, + unknown_lookup_prefix="UNKNOWN_TERRAIN_RESTRICTION" )), # determines whether the unit can fly (READ_GEN, "fly_mode", StorageType.BOOLEAN_MEMBER, "int8_t"), @@ -394,30 +404,35 @@ def get_data_format_members( # blast_attack_level. raw_type="int8_t", type_name="blast_types", - lookup_dict=BLAST_DEFENSE_TYPES + lookup_dict=BLAST_DEFENSE_TYPES, + unknown_lookup_prefix="UNKNOWN_BLAST_DEFENSE_TYPE" )), (SKIP, "combat_level", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int8_t", type_name="combat_levels", - lookup_dict=COMBAT_LEVELS + lookup_dict=COMBAT_LEVELS, + unknown_lookup_prefix="UNKNOWN_COMBAT_LEVEL" )), (READ_GEN, "interaction_mode", StorageType.ID_MEMBER, EnumLookupMember( # what can be done with this unit? raw_type="int8_t", type_name="interaction_modes", - lookup_dict=INTERACTION_MODES + lookup_dict=INTERACTION_MODES, + unknown_lookup_prefix="UNKNOWN_INTERACTION_MODE" )), (SKIP, "map_draw_level", StorageType.ID_MEMBER, EnumLookupMember( # how does the unit show up on the minimap? raw_type="int8_t", type_name="minimap_modes", - lookup_dict=MINIMAP_MODES + lookup_dict=MINIMAP_MODES, + unknown_lookup_prefix="UNKNOWN_MINIMAP_MODE" )), (SKIP, "unit_level", StorageType.ID_MEMBER, EnumLookupMember( # selects the available ui command buttons for the unit raw_type="int8_t", type_name="command_attributes", - lookup_dict=UNIT_LEVELS + lookup_dict=UNIT_LEVELS, + unknown_lookup_prefix="UNKNOWN_UNIT_LEVEL" )), (SKIP, "attack_reaction", StorageType.FLOAT_MEMBER, "float"), # palette color id for the minimap @@ -443,7 +458,8 @@ def get_data_format_members( (READ_GEN, "obstruction_type", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int8_t", type_name="obstruction_types", - lookup_dict=OBSTRUCTION_TYPES + lookup_dict=OBSTRUCTION_TYPES, + unknown_lookup_prefix="UNKNOWN_OBSTRUCTION_TYPE" )), (READ_GEN, "obstruction_class", StorageType.ID_MEMBER, "int8_t"), @@ -466,7 +482,8 @@ def get_data_format_members( (READ_GEN, "obstruction_type", StorageType.ID_MEMBER, EnumLookupMember( raw_type="int8_t", type_name="obstruction_types", - lookup_dict=OBSTRUCTION_TYPES + lookup_dict=OBSTRUCTION_TYPES, + unknown_lookup_prefix="UNKNOWN_OBSTRUCTION_TYPE" )), (READ_GEN, "obstruction_class", StorageType.ID_MEMBER, "int8_t"), ]) @@ -476,7 +493,8 @@ def get_data_format_members( # things that happen when the unit was selected raw_type="int8_t", type_name="selection_effects", - lookup_dict=SELECTION_EFFECTS + lookup_dict=SELECTION_EFFECTS, + unknown_lookup_prefix="UNKNOWN_SELECTION_EFFECT" )), # 0: default, -16: fish trap, farm, 52: deadfarm, OLD-*, 116: flare, # whale, dolphin -123: fish @@ -518,7 +536,8 @@ def get_data_format_members( (SKIP, "old_attack_mode", StorageType.ID_MEMBER, EnumLookupMember( # obsolete, as it's copied when converting the unit raw_type="int8_t", # things that happen when the unit was selected type_name="attack_modes", - lookup_dict=ATTACK_MODES + lookup_dict=ATTACK_MODES, + unknown_lookup_prefix="UNKNOWN_ATTACK_MODE" )), # leftover from alpha. would et units change terrain under them (SKIP, "convert_terrain", StorageType.INT_MEMBER, "int8_t"), @@ -781,7 +800,8 @@ def get_data_format_members( # the accessible values on the specified terrain restriction raw_type="int16_t", type_name="boundary_ids", - lookup_dict=BOUNDARY_IDS + lookup_dict=BOUNDARY_IDS, + unknown_lookup_prefix="UNKNOWN_BOUNDARY_ID" )), ]) @@ -807,7 +827,8 @@ def get_data_format_members( # blasts damage units that have higher or same blast_defense_level raw_type="uint8_t", type_name="range_damage_type", - lookup_dict=BLAST_OFFENSE_TYPES + lookup_dict=BLAST_OFFENSE_TYPES, + unknown_lookup_prefix="UNKNOWN_BLAST_OFFENSE_TYPE" )), # minimum range that this projectile requests for display (READ_GEN, "weapon_range_min", StorageType.FLOAT_MEMBER, "float"), @@ -825,7 +846,14 @@ def get_data_format_members( ]) if game_version.edition.game_id == "AOE2DE": - data_format.append((READ_GEN, "blast_damage", StorageType.FLOAT_MEMBER, "float")) + data_format.extend([ + (READ_GEN, "blast_damage", StorageType.FLOAT_MEMBER, "float"), + (SKIP, "damage_reflect", StorageType.FLOAT_MEMBER, "float"), + (SKIP, "friendly_fire_damage", StorageType.FLOAT_MEMBER, "float"), + (SKIP, "interrupt_frame", StorageType.INT_MEMBER, "int16_t"), + (SKIP, "garrison_firepower", StorageType.FLOAT_MEMBER, "float"), + (SKIP, "attack_graphic1", StorageType.ID_MEMBER, "int16_t"), + ]) return data_format @@ -923,7 +951,8 @@ def get_data_format_members( (READ_GEN, "creatable_type", StorageType.ID_MEMBER, EnumLookupMember( raw_type="uint8_t", type_name="creatable_types", - lookup_dict=CREATABLE_TYPES + lookup_dict=CREATABLE_TYPES, + unknown_lookup_prefix="UNKNOWN_CREATABLE_TYPE" )), # if building: "others" tab in editor, if living unit: "heroes" tab, # regenerate health + monk immunity @@ -939,10 +968,24 @@ def get_data_format_members( (READ_GEN, "spawn_graphic_id", StorageType.ID_MEMBER, "int16_t"), (READ_GEN, "upgrade_graphic_id", StorageType.ID_MEMBER, "int16_t"), (READ_GEN, "hero_glow_graphic_id", StorageType.ID_MEMBER, "int16_t"), + (READ_GEN, "idle_attack_graphic_id", StorageType.ID_MEMBER, "int16_t"), (READ_GEN, "max_charge", StorageType.FLOAT_MEMBER, "float"), (READ_GEN, "charge_regen_rate", StorageType.FLOAT_MEMBER, "float"), (READ_GEN, "charge_cost", StorageType.ID_MEMBER, "int16_t"), (READ_GEN, "charge_type", StorageType.ID_MEMBER, "int16_t"), + + (READ_GEN, "charge_target", StorageType.ID_MEMBER, "int16_t"), + (READ_GEN, "charge_projectile_id", StorageType.ID_MEMBER, "int32_t"), + (READ_GEN, "attack_priority", StorageType.ID_MEMBER, "int8_t"), + (READ_GEN, "invulnerability_level", StorageType.FLOAT_MEMBER, "float"), + + (READ_GEN, "button_icon_id", StorageType.ID_MEMBER, "int16_t"), + # Short tooltip + (READ_GEN, "button_tooltip_id0", StorageType.ID_MEMBER, "int32_t"), + # Extended tooltip + (READ_GEN, "button_tooltip_id1", StorageType.ID_MEMBER, "int32_t"), + (READ_GEN, "button_hotkey_action", StorageType.ID_MEMBER, "int16_t"), + (READ_GEN, "min_convert_mod", StorageType.FLOAT_MEMBER, "float"), (READ_GEN, "max_convert_mod", StorageType.FLOAT_MEMBER, "float"), (READ_GEN, "convert_chance_mod", StorageType.FLOAT_MEMBER, "float"), @@ -1054,7 +1097,8 @@ def get_data_format_members( (READ_GEN, "garrison_type", StorageType.BITFIELD_MEMBER, EnumLookupMember( raw_type="int8_t", type_name="garrison_types", - lookup_dict=GARRISON_TYPES + lookup_dict=GARRISON_TYPES, + unknown_lookup_prefix="UNKNOWN_GARRISON_TYPE" )), (READ_GEN, "garrison_heal_rate", StorageType.FLOAT_MEMBER, "float"), (SKIP, "garrison_repair_rate", StorageType.FLOAT_MEMBER, "float"), From 62f1d4ee742d22f68c96f0304937daa4d903f0d7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 22 Jun 2025 18:55:02 +0200 Subject: [PATCH 157/163] convert: Avoid multiple reads on enum lookup members. --- .../value_object/read/genie_structure.py | 296 ++++++++++-------- 1 file changed, 159 insertions(+), 137 deletions(-) diff --git a/openage/convert/value_object/read/genie_structure.py b/openage/convert/value_object/read/genie_structure.py index dcfd0f3baf..ad37531f93 100644 --- a/openage/convert/value_object/read/genie_structure.py +++ b/openage/convert/value_object/read/genie_structure.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. # TODO pylint: disable=C,R @@ -405,7 +405,7 @@ def _read_primitive( f"unknown data member definition {var_type} for member '{var_name}'") if data_count < 0: - raise SyntaxError("invalid length %d < 0 in %s for member '%s'" % ( + raise ValueError("invalid length %d < 0 in %s for member '%s'" % ( data_count, var_type, var_name)) if struct_type not in STRUCT_TYPE_LOOKUP: @@ -420,182 +420,204 @@ def _read_primitive( # lookup c type to python struct scan type symbol = STRUCT_TYPE_LOOKUP[struct_type] - # read that stuff!!11 + # figure out the data format struct_format = "< %d%s" % (data_count, symbol) - if export != SKIP: - result = struct.unpack_from(struct_format, raw, offset) + if export == SKIP: + # just calculate the offset and skip the reading process + offset += struct.calcsize(struct_format) - if is_custom_member: - if not var_type.verify_read_data(self, result): - raise SyntaxError("invalid data when reading %s " - "at offset %# 08x" % (var_name, offset)) + return offset, [], False - # TODO: move these into a read entry hook/verification method - if symbol == "s": - # stringify char array - result = decode_until_null(result[0]) + # else, read that stuff!!11 + unpacked = struct.unpack_from(struct_format, raw, offset) - if export == READ_GEN: - if storage_type is StorageType.STRING_MEMBER: - gen_member = StringMember(var_name, result) + if is_custom_member: + if not var_type.verify_read_data(self, unpacked): + raise ValueError("invalid data when reading %s " + "at offset %# 08x" % (var_name, offset)) - else: - raise SyntaxError("%s at offset %# 08x: Data read via %s " - "cannot be stored as %s;" - " expected %s" - % (var_name, offset, var_type, storage_type, - StorageType.STRING_MEMBER)) + # TODO: move these into a read entry hook/verification method + if symbol == "s": + # stringify char array + result = decode_until_null(unpacked[0]) - generated_value_members.append(gen_member) + if export == READ_GEN: + if storage_type is StorageType.STRING_MEMBER: + gen_member = StringMember(var_name, result) - elif is_array: - if export == READ_GEN: - # Turn every element of result into a member - # and put them into an array - array_members = [] - allowed_member_type = None + else: + raise SyntaxError("%s at offset %# 08x: Data read via %s " + "cannot be stored as %s;" + " expected %s" + % (var_name, offset, var_type, storage_type, + StorageType.STRING_MEMBER)) + + generated_value_members.append(gen_member) + elif is_array: + result = unpacked + + if export == READ_GEN: + # Turn every element of result into a member + # and put them into an array + array_members = [] + allowed_member_type = None + + if storage_type is StorageType.ARRAY_INT: + allowed_member_type = StorageType.INT_MEMBER + + elif storage_type is StorageType.ARRAY_FLOAT: + allowed_member_type = StorageType.FLOAT_MEMBER + + elif storage_type is StorageType.ARRAY_BOOL: + allowed_member_type = StorageType.BOOLEAN_MEMBER + + elif storage_type is StorageType.ARRAY_ID: + allowed_member_type = StorageType.ID_MEMBER + + elif storage_type is StorageType.ARRAY_STRING: + allowed_member_type = StorageType.STRING_MEMBER + + for elem in unpacked: if storage_type is StorageType.ARRAY_INT: - allowed_member_type = StorageType.INT_MEMBER + gen_member = IntMember(var_name, elem) + array_members.append(gen_member) elif storage_type is StorageType.ARRAY_FLOAT: - allowed_member_type = StorageType.FLOAT_MEMBER + gen_member = FloatMember(var_name, elem) + array_members.append(gen_member) elif storage_type is StorageType.ARRAY_BOOL: - allowed_member_type = StorageType.BOOLEAN_MEMBER + gen_member = BooleanMember(var_name, elem) + array_members.append(gen_member) elif storage_type is StorageType.ARRAY_ID: - allowed_member_type = StorageType.ID_MEMBER + gen_member = IDMember(var_name, elem) + array_members.append(gen_member) elif storage_type is StorageType.ARRAY_STRING: - allowed_member_type = StorageType.STRING_MEMBER + gen_member = StringMember(var_name, elem) + array_members.append(gen_member) + + else: + raise SyntaxError("%s at offset %# 08x: Data read via %s " + "cannot be stored as %s;" + " expected %s, %s, %s, %s or %s" + % (var_name, offset, var_type, storage_type, + StorageType.ARRAY_INT, + StorageType.ARRAY_FLOAT, + StorageType.ARRAY_BOOL, + StorageType.ARRAY_ID, + StorageType.ARRAY_STRING)) + + # Create the array + array = ArrayMember(var_name, allowed_member_type, array_members) + generated_value_members.append(array) + + elif data_count == 1: + # store first tuple element + result = unpacked[0] + + if symbol == "f": + if not math.isfinite(result): + raise SyntaxError("invalid float when " + "reading %s at offset %# 08x" % ( + var_name, offset)) + + if is_custom_member: + # save the lookup key / plain value (used for some storage types) + lookup_key = result - for elem in result: - if storage_type is StorageType.ARRAY_INT: - gen_member = IntMember(var_name, elem) - array_members.append(gen_member) + # do an additional lookup to get the stored enum value + result = var_type.entry_hook(result) - elif storage_type is StorageType.ARRAY_FLOAT: - gen_member = FloatMember(var_name, elem) - array_members.append(gen_member) + if isinstance(var_type, EnumLookupMember): + if export == READ_GEN: + # store differently depending on storage type + if storage_type is StorageType.INT_MEMBER: + # store as plain integer value + gen_member = IntMember(var_name, lookup_key) - elif storage_type is StorageType.ARRAY_BOOL: - gen_member = BooleanMember(var_name, elem) - array_members.append(gen_member) + elif storage_type is StorageType.ID_MEMBER: + # store as plain integer value + gen_member = IDMember(var_name, lookup_key) - elif storage_type is StorageType.ARRAY_ID: - gen_member = IDMember(var_name, elem) - array_members.append(gen_member) + elif storage_type is StorageType.BITFIELD_MEMBER: + # store as plain integer value + gen_member = BitfieldMember(var_name, lookup_key) - elif storage_type is StorageType.ARRAY_STRING: - gen_member = StringMember(var_name, elem) - array_members.append(gen_member) + elif storage_type is StorageType.STRING_MEMBER: + # store by looking up value from dict + gen_member = StringMember(var_name, result) else: raise SyntaxError("%s at offset %# 08x: Data read via %s " "cannot be stored as %s;" - " expected %s, %s, %s, %s or %s" + " expected %s, %s, %s or %s" % (var_name, offset, var_type, storage_type, - StorageType.ARRAY_INT, - StorageType.ARRAY_FLOAT, - StorageType.ARRAY_BOOL, - StorageType.ARRAY_ID, - StorageType.ARRAY_STRING)) - - # Create the array - array = ArrayMember(var_name, allowed_member_type, array_members) - generated_value_members.append(array) + StorageType.INT_MEMBER, + StorageType.ID_MEMBER, + StorageType.BITFIELD_MEMBER, + StorageType.STRING_MEMBER)) - elif data_count == 1: - # store first tuple element - result = result[0] + generated_value_members.append(gen_member) - if symbol == "f": - if not math.isfinite(result): - raise SyntaxError("invalid float when " - "reading %s at offset %# 08x" % ( - var_name, offset)) + elif isinstance(var_type, ContinueReadMember): + if result == ContinueReadMember.result.ABORT: + # don't go through all other members of this class! + stop_reading_members = True - if export == READ_GEN: - # Store the member as ValueMember - if is_custom_member: - lookup_result = var_type.entry_hook(result) - - if isinstance(var_type, EnumLookupMember): - # store differently depending on storage type - if storage_type is StorageType.INT_MEMBER: - # store as plain integer value - gen_member = IntMember(var_name, result) - - elif storage_type is StorageType.ID_MEMBER: - # store as plain integer value - gen_member = IDMember(var_name, result) - - elif storage_type is StorageType.BITFIELD_MEMBER: - # store as plain integer value - gen_member = BitfieldMember(var_name, result) - - elif storage_type is StorageType.STRING_MEMBER: - # store by looking up value from dict - gen_member = StringMember(var_name, lookup_result) - - else: - raise SyntaxError("%s at offset %# 08x: Data read via %s " - "cannot be stored as %s;" - " expected %s, %s, %s or %s" - % (var_name, offset, var_type, storage_type, - StorageType.INT_MEMBER, - StorageType.ID_MEMBER, - StorageType.BITFIELD_MEMBER, - StorageType.STRING_MEMBER)) - - elif isinstance(var_type, ContinueReadMember): - if storage_type is StorageType.BOOLEAN_MEMBER: - gen_member = StringMember(var_name, lookup_result) - - else: - raise SyntaxError("%s at offset %# 08x: Data read via %s " - "cannot be stored as %s;" - " expected %s" - % (var_name, offset, var_type, storage_type, - StorageType.BOOLEAN_MEMBER)) - - else: - if storage_type is StorageType.INT_MEMBER: - gen_member = IntMember(var_name, result) - - elif storage_type is StorageType.FLOAT_MEMBER: - gen_member = FloatMember(var_name, result) - - elif storage_type is StorageType.BOOLEAN_MEMBER: - gen_member = BooleanMember(var_name, result) - - elif storage_type is StorageType.ID_MEMBER: - gen_member = IDMember(var_name, result) + if export == READ_GEN: + if storage_type is StorageType.BOOLEAN_MEMBER: + gen_member = StringMember(var_name, result) else: raise SyntaxError("%s at offset %# 08x: Data read via %s " "cannot be stored as %s;" - " expected %s, %s, %s or %s" + " expected %s" % (var_name, offset, var_type, storage_type, - StorageType.INT_MEMBER, - StorageType.FLOAT_MEMBER, - StorageType.BOOLEAN_MEMBER, - StorageType.ID_MEMBER)) + StorageType.BOOLEAN_MEMBER)) + + generated_value_members.append(gen_member) + + else: + if export == READ_GEN: + if storage_type is StorageType.INT_MEMBER: + gen_member = IntMember(var_name, result) + + elif storage_type is StorageType.FLOAT_MEMBER: + gen_member = FloatMember(var_name, result) + + elif storage_type is StorageType.BOOLEAN_MEMBER: + gen_member = BooleanMember(var_name, result) + + elif storage_type is StorageType.ID_MEMBER: + gen_member = IDMember(var_name, result) + + else: + raise SyntaxError("%s at offset %# 08x: Data read via %s " + "cannot be stored as %s;" + " expected %s, %s, %s or %s" + % (var_name, offset, var_type, storage_type, + StorageType.INT_MEMBER, + StorageType.FLOAT_MEMBER, + StorageType.BOOLEAN_MEMBER, + StorageType.ID_MEMBER)) generated_value_members.append(gen_member) - # run entry hook for non-primitive members - if is_custom_member: - result = var_type.entry_hook(result) + # run entry hook for non-primitive members + # if isinstance(var_type, ContinueReadMember) and is_custom_member: + # if is_custom_member: + # result = var_type.entry_hook(unpacked) - if result == ContinueReadMember.result.ABORT: - # don't go through all other members of this class! - stop_reading_members = True + # if result == ContinueReadMember.result.ABORT: + # # don't go through all other members of this class! + # stop_reading_members = True - # store member's data value - setattr(self, var_name, result) + # store member's data value + setattr(self, var_name, result) # increase the current file position by the size we just read offset += struct.calcsize(struct_format) From 96b62cd87ce2ecf48a2873c409353130ab6927bd Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 22 Jun 2025 23:35:41 +0200 Subject: [PATCH 158/163] convert: Ignore duplicates for container of DE2 attacks. Huskarl unit has two entries for armor class 15 (probably an error). --- .../conversion/de2/main/extract/unit.py | 2 +- .../value_object/read/value_members.py | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/openage/convert/processor/conversion/de2/main/extract/unit.py b/openage/convert/processor/conversion/de2/main/extract/unit.py index df28ef9951..cc8d5bbe16 100644 --- a/openage/convert/processor/conversion/de2/main/extract/unit.py +++ b/openage/convert/processor/conversion/de2/main/extract/unit.py @@ -36,7 +36,7 @@ def extract_genie_units(gamespec: ArrayMember, full_data_set: GenieObjectContain # Turn attack and armor into containers to make diffing work if "attacks" in unit_members.keys(): attacks_member = unit_members.pop("attacks") - attacks_member = attacks_member.get_container("type_id") + attacks_member = attacks_member.get_container("type_id", ignore_duplicates=True) armors_member = unit_members.pop("armors") armors_member = armors_member.get_container("type_id") diff --git a/openage/convert/value_object/read/value_members.py b/openage/convert/value_object/read/value_members.py index db9968d05b..7942cc1e20 100644 --- a/openage/convert/value_object/read/value_members.py +++ b/openage/convert/value_object/read/value_members.py @@ -1,4 +1,4 @@ -# Copyright 2019-2023 the openage authors. See copying.md for legal info. +# Copyright 2019-2025 the openage authors. See copying.md for legal info. # TODO pylint: disable=C,R,abstract-method """ @@ -30,6 +30,7 @@ from abc import ABC, abstractmethod from .dynamic_loader import DynamicLoader +from ....log import warn class ValueMember(ABC): @@ -432,22 +433,24 @@ def get_type(self) -> StorageType: def get_container( self, - key_member_name: str, - force_not_found: bool = False, - force_duplicate: bool = False + key_member_id: str, + /, + ignore_not_found: bool = False, + ignore_duplicates: bool = False ) -> ContainerMember: """ - Returns a ContainerMember generated from an array with type ARRAY_CONTAINER. + Get a ContainerMember generated from an array with type ARRAY_CONTAINER. It uses the values of the members with the specified name as keys. + By default, this method raises an exception if a member with this name does not exist or the same key is used twice. - :param key_member_name: A member in the containers whos value is used as the key. - :type key_member_name: str - :param force_not_found: Do not raise an exception if the member is not found. - :type force_not_found: bool - :param force_duplicate: Do not raise an exception if the same key value is used twice. - :type force_duplicate: bool + :param key_member_id: A member in the containers whos value is used as the key. + :param ignore_not_found: Warn instead of raise if \p key_member_id is not found in + a container. + :param ignore_duplicates: Warn instead of raise if \p key_member_id is present + multiple times. In that case, only the first value is used. + :return: ContainerMember with the keys as values of the specified member. """ if self.get_type() is not StorageType.ARRAY_CONTAINER: raise TypeError("%s: Container can only be generated from arrays with" @@ -456,21 +459,25 @@ def get_container( member_dict = {} for container in self.value: - if key_member_name not in container.value.keys(): - if force_not_found: + if key_member_id not in container.value.keys(): + if ignore_not_found: + warn("%s: Container %s has no member called %s" + % (self, container, key_member_id)) continue raise KeyError("%s: Container %s has no member called %s" - % (self, container, key_member_name)) + % (self, container, key_member_id)) - key_member_value = container[key_member_name].value + key_member_value = container[key_member_id].value if key_member_value in member_dict.keys(): - if force_duplicate: + if ignore_duplicates: + warn("%s: Duplicate key %s for container member %s" + % (self, key_member_value, key_member_id)) continue raise KeyError("%s: Duplicate key %s for container member %s" - % (self, key_member_value, key_member_name)) + % (self, key_member_value, key_member_id)) member_dict.update({key_member_value: container}) From 6c26806b42df619691219a55d1d486e2b8e1cd00 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 23 Jun 2025 00:01:32 +0200 Subject: [PATCH 159/163] convert: Allow conversion of additional task groups. --- .../entity_object/conversion/aoc/genie_unit.py | 16 ++++++++-------- .../entity_object/conversion/ror/genie_unit.py | 5 +---- .../conversion/aoc/main/groups/villager_group.py | 14 +++++--------- .../conversion/ror/main/groups/entity_line.py | 6 +++--- .../swgbcc/main/groups/villager_group.py | 12 ++++-------- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/openage/convert/entity_object/conversion/aoc/genie_unit.py b/openage/convert/entity_object/conversion/aoc/genie_unit.py index 52ded9b659..b44dcee010 100644 --- a/openage/convert/entity_object/conversion/aoc/genie_unit.py +++ b/openage/convert/entity_object/conversion/aoc/genie_unit.py @@ -1,4 +1,4 @@ -# Copyright 2019-2023 the openage authors. See copying.md for legal info. +# Copyright 2019-2025 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-public-methods,too-many-instance-attributes,consider-iterating-dictionary @@ -1041,11 +1041,12 @@ class GenieUnitTaskGroup(GenieUnitLineGroup): __slots__ = ('task_group_id',) - # From unit connection - male_line_id = 83 # male villager (with combat task) - - # Female villagers have no line obj_id, so we use the combat unit - female_line_id = 293 # female villager (with combat task) + # Maps task group ID to the line ID of the first unit in the group. + line_id_assignments = { + 1: 83, # male villager (with combat task) + 2: 293, # female villager (with combat task) + 3: 13, # fishing ship (in DE2) + } def __init__( self, @@ -1074,8 +1075,7 @@ def add_unit( after: GenieUnitObject = None ) -> None: # Force the idle/combat units at the beginning of the line - if genie_unit["id0"].value in (GenieUnitTaskGroup.male_line_id, - GenieUnitTaskGroup.female_line_id): + if genie_unit["id0"].value in self.line_id_assignments.values(): super().add_unit(genie_unit, 0, after) else: diff --git a/openage/convert/entity_object/conversion/ror/genie_unit.py b/openage/convert/entity_object/conversion/ror/genie_unit.py index c2fe46267f..89ad7ebe11 100644 --- a/openage/convert/entity_object/conversion/ror/genie_unit.py +++ b/openage/convert/entity_object/conversion/ror/genie_unit.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. """ Contains structures and API-like objects for game entities from RoR. @@ -200,9 +200,6 @@ class RoRUnitTaskGroup(GenieUnitTaskGroup): Example: Villager """ - # Female villagers do not exist in RoR - female_line_id = -1 - __slots__ = ('enabling_research_id',) def __init__( diff --git a/openage/convert/processor/conversion/aoc/main/groups/villager_group.py b/openage/convert/processor/conversion/aoc/main/groups/villager_group.py index 317bc86ef0..7b1ea9d574 100644 --- a/openage/convert/processor/conversion/aoc/main/groups/villager_group.py +++ b/openage/convert/processor/conversion/aoc/main/groups/villager_group.py @@ -18,9 +18,8 @@ def create_villager_groups(full_data_set: GenieObjectContainer) -> None: villager group. :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. - :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer + contains all relevant data for the conversion + process. """ units = full_data_set.genie_units task_group_ids = set() @@ -43,11 +42,8 @@ def create_villager_groups(full_data_set: GenieObjectContainer) -> None: task_group.add_unit(unit) else: - if task_group_id == 1: - line_id = GenieUnitTaskGroup.male_line_id - - elif task_group_id == 2: - line_id = GenieUnitTaskGroup.female_line_id + if task_group_id in GenieUnitTaskGroup.line_id_assignments: + line_id = GenieUnitTaskGroup.line_id_assignments[task_group_id] else: raise ValueError( @@ -62,7 +58,7 @@ def create_villager_groups(full_data_set: GenieObjectContainer) -> None: unit_ids.add(unit["id0"].value) # Create the villager task group - villager = GenieVillagerGroup(118, task_group_ids, full_data_set) + villager = GenieVillagerGroup(118, (1, 2), full_data_set) full_data_set.unit_lines.update({villager.get_id(): villager}) full_data_set.villager_groups.update({villager.get_id(): villager}) for unit_id in unit_ids: diff --git a/openage/convert/processor/conversion/ror/main/groups/entity_line.py b/openage/convert/processor/conversion/ror/main/groups/entity_line.py index d9ce861f38..c41227656c 100644 --- a/openage/convert/processor/conversion/ror/main/groups/entity_line.py +++ b/openage/convert/processor/conversion/ror/main/groups/entity_line.py @@ -51,8 +51,8 @@ def create_entity_lines(gamespec: ArrayMember, full_data_set: GenieObjectContain task_group.add_unit(entity) else: - if task_group_id == 1: - line_id = RoRUnitTaskGroup.male_line_id + if task_group_id in RoRUnitTaskGroup.line_id_assignments: + line_id = RoRUnitTaskGroup.line_id_assignments[task_group_id] task_group = RoRUnitTaskGroup(line_id, task_group_id, -1, full_data_set) task_group.add_unit(entity) @@ -72,7 +72,7 @@ def create_entity_lines(gamespec: ArrayMember, full_data_set: GenieObjectContain full_data_set.unit_ref.update({unit_id: building_line}) # Create the villager task group - villager = RoRVillagerGroup(118, task_group_ids, full_data_set) + villager = RoRVillagerGroup(118, (1,), full_data_set) full_data_set.unit_lines.update({villager.get_id(): villager}) full_data_set.villager_groups.update({villager.get_id(): villager}) for unit_id in villager_unit_ids: diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py b/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py index 441a4fad4f..a4064ba240 100644 --- a/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py +++ b/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py @@ -42,13 +42,9 @@ def create_villager_groups(full_data_set: GenieObjectContainer) -> None: task_group.add_unit(unit) else: - if task_group_id == 1: - # SWGB uses the same IDs as AoC - line_id = GenieUnitTaskGroup.male_line_id - - elif task_group_id == 2: - # No differences to task group 1; probably unused - continue + if task_group_id in GenieUnitTaskGroup.line_id_assignments: + # SWGB uses the same IDs as AoC for lines + line_id = GenieUnitTaskGroup.line_id_assignments[task_group_id] else: raise ValueError( @@ -63,7 +59,7 @@ def create_villager_groups(full_data_set: GenieObjectContainer) -> None: unit_ids.add(unit["id0"].value) # Create the villager task group - villager = GenieVillagerGroup(118, task_group_ids, full_data_set) + villager = GenieVillagerGroup(118, (1,), full_data_set) full_data_set.unit_lines.update({villager.get_id(): villager}) # TODO: Find the line id elsewhere full_data_set.unit_lines_vertical_ref.update({36: villager}) From 94cd02513a43828f307e5bcec440c011e801304a Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 23 Jun 2025 00:05:52 +0200 Subject: [PATCH 160/163] convert: Remove unused member from GenieObjectContainer. --- .../entity_object/conversion/aoc/genie_object_container.py | 4 +--- .../conversion/swgbcc/main/groups/villager_group.py | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/openage/convert/entity_object/conversion/aoc/genie_object_container.py b/openage/convert/entity_object/conversion/aoc/genie_object_container.py index 00ce6cd79e..2dccf39ad4 100644 --- a/openage/convert/entity_object/conversion/aoc/genie_object_container.py +++ b/openage/convert/entity_object/conversion/aoc/genie_object_container.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=too-many-instance-attributes,too-few-public-methods @@ -84,8 +84,6 @@ def __init__(self): # Keys are the ID of the first unit in line self.unit_lines: dict[int, GenieUnitLineGroup] = {} - # Keys are the line ID of the unit connection - self.unit_lines_vertical_ref: dict[int, GenieUnitLineGroup] = {} self.building_lines: dict[int, GenieBuildingLineGroup] = {} self.task_groups: dict[int, GenieUnitTaskGroup] = {} self.transform_groups: dict[int, GenieUnitTransformGroup] = {} diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py b/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py index a4064ba240..6f9270197b 100644 --- a/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py +++ b/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py @@ -18,8 +18,8 @@ def create_villager_groups(full_data_set: GenieObjectContainer) -> None: villager group. :param full_data_set: GenieObjectContainer instance that - contains all relevant data for the conversion - process. + contains all relevant data for the conversion + process. """ units = full_data_set.genie_units task_group_ids = set() @@ -61,8 +61,6 @@ def create_villager_groups(full_data_set: GenieObjectContainer) -> None: # Create the villager task group villager = GenieVillagerGroup(118, (1,), full_data_set) full_data_set.unit_lines.update({villager.get_id(): villager}) - # TODO: Find the line id elsewhere - full_data_set.unit_lines_vertical_ref.update({36: villager}) full_data_set.villager_groups.update({villager.get_id(): villager}) for unit_id in unit_ids: full_data_set.unit_ref.update({unit_id: villager}) From 7e5d53eaa4aa42292cff0be66ccea436982f0ceb Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 6 Jul 2025 07:09:43 +0200 Subject: [PATCH 161/163] convert: Add task groups to unit lines. --- .../processor/conversion/aoc/main/groups/villager_group.py | 1 + .../processor/conversion/swgbcc/main/groups/villager_group.py | 1 + 2 files changed, 2 insertions(+) diff --git a/openage/convert/processor/conversion/aoc/main/groups/villager_group.py b/openage/convert/processor/conversion/aoc/main/groups/villager_group.py index 7b1ea9d574..8729ef8976 100644 --- a/openage/convert/processor/conversion/aoc/main/groups/villager_group.py +++ b/openage/convert/processor/conversion/aoc/main/groups/villager_group.py @@ -53,6 +53,7 @@ def create_villager_groups(full_data_set: GenieObjectContainer) -> None: task_group = GenieUnitTaskGroup(line_id, task_group_id, full_data_set) task_group.add_unit(unit) full_data_set.task_groups.update({task_group_id: task_group}) + full_data_set.unit_lines.update({task_group.get_id(): task_group}) task_group_ids.add(task_group_id) unit_ids.add(unit["id0"].value) diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py b/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py index 6f9270197b..a85c3e93bb 100644 --- a/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py +++ b/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py @@ -54,6 +54,7 @@ def create_villager_groups(full_data_set: GenieObjectContainer) -> None: task_group = GenieUnitTaskGroup(line_id, task_group_id, full_data_set) task_group.add_unit(unit) full_data_set.task_groups.update({task_group_id: task_group}) + full_data_set.unit_lines.update({task_group.get_id(): task_group}) task_group_ids.add(task_group_id) unit_ids.add(unit["id0"].value) From 740354aa2279aee1d58f5f477a4226cd4aa05038 Mon Sep 17 00:00:00 2001 From: heinezen Date: Mon, 7 Jul 2025 07:28:14 +0200 Subject: [PATCH 162/163] convert: Create stack building groups even if only head building is in connections. --- .../conversion/aoc/genie_object_container.py | 3 +- .../conversion/aoc/genie_unit.py | 28 +++++++++++++++++-- .../aoc/main/groups/building_line.py | 26 +++++++++++++---- .../conversion/aoc/main/link/gather.py | 12 +++++++- .../swgbcc/main/groups/building_line.py | 1 + .../value_object/read/media/datfile/unit.py | 4 +-- 6 files changed, 61 insertions(+), 13 deletions(-) diff --git a/openage/convert/entity_object/conversion/aoc/genie_object_container.py b/openage/convert/entity_object/conversion/aoc/genie_object_container.py index 2dccf39ad4..83fa036804 100644 --- a/openage/convert/entity_object/conversion/aoc/genie_object_container.py +++ b/openage/convert/entity_object/conversion/aoc/genie_object_container.py @@ -32,7 +32,7 @@ from openage.convert.entity_object.conversion.aoc.genie_unit import GenieUnitObject, \ GenieAmbientGroup, GenieBuildingLineGroup, GenieMonkGroup, GenieUnitLineGroup, \ GenieUnitTaskGroup, GenieUnitTransformGroup, GenieVariantGroup, GenieVillagerGroup, \ - GenieGameEntityGroup + GenieGameEntityGroup, GenieStackBuildingGroup from openage.convert.entity_object.export.media_export_request import MediaExportRequest from openage.convert.entity_object.export.metadata_export import MetadataExport from openage.convert.value_object.init.game_version import GameVersion @@ -85,6 +85,7 @@ def __init__(self): # Keys are the ID of the first unit in line self.unit_lines: dict[int, GenieUnitLineGroup] = {} self.building_lines: dict[int, GenieBuildingLineGroup] = {} + self.stack_building_groups: dict[int, GenieStackBuildingGroup] = {} self.task_groups: dict[int, GenieUnitTaskGroup] = {} self.transform_groups: dict[int, GenieUnitTransformGroup] = {} self.villager_groups: dict[int, GenieVillagerGroup] = {} diff --git a/openage/convert/entity_object/conversion/aoc/genie_unit.py b/openage/convert/entity_object/conversion/aoc/genie_unit.py index b44dcee010..90457d523e 100644 --- a/openage/convert/entity_object/conversion/aoc/genie_unit.py +++ b/openage/convert/entity_object/conversion/aoc/genie_unit.py @@ -750,21 +750,21 @@ class GenieStackBuildingGroup(GenieBuildingLineGroup): def __init__( self, stack_unit_id: int, - head_building_id: int, + head_unit_id: int, full_data_set: GenieObjectContainer, ): """ Creates a new Genie building line. :param stack_unit_id: "Actual" building that appears when constructed. - :param head_building_id: The building used during construction. + :param head_unit_id: The building used during construction. :param full_data_set: GenieObjectContainer instance that contains all relevant data for the conversion process. """ super().__init__(stack_unit_id, full_data_set) - self.head = self.data.genie_units[head_building_id] + self.head = self.data.genie_units[head_unit_id] self.stack = self.data.genie_units[stack_unit_id] def is_creatable(self, civ_id: int = -1) -> bool: @@ -827,6 +827,28 @@ def get_train_location_id(self) -> int: return None + def get_head_annex_ids(self) -> list[int]: + """ + Returns the unit IDs of annexes for the head building. + """ + annexes = self.head["building_annex"].value + annex_ids = [] + for annex in annexes: + annex_ids.append(annex["unit_id"].value) + + return annex_ids + + def get_stack_annex_ids(self) -> list[int]: + """ + Returns the unit IDs of annexes for the stack unit. + """ + annexes = self.stack["building_annex"].value + annex_ids = [] + for annex in annexes: + annex_ids.append(annex["unit_id"].value) + + return annex_ids + def __repr__(self): return f"GenieStackBuildingGroup<{self.get_id()}>" diff --git a/openage/convert/processor/conversion/aoc/main/groups/building_line.py b/openage/convert/processor/conversion/aoc/main/groups/building_line.py index ccbe6dbf6c..ffa453af46 100644 --- a/openage/convert/processor/conversion/aoc/main/groups/building_line.py +++ b/openage/convert/processor/conversion/aoc/main/groups/building_line.py @@ -31,7 +31,11 @@ def create_building_lines(full_data_set: GenieObjectContainer) -> None: building_id = connection["id"].value building = full_data_set.genie_units[building_id] previous_building_id = None - stack_building = False + + # Building is the head of a stack building + stack_building_head = False + # Building is an annex of a stack building + stack_building_annex = False # Buildings have no actual lines, so we use # their unit ID as the line ID. @@ -40,13 +44,11 @@ def create_building_lines(full_data_set: GenieObjectContainer) -> None: # Check if we have to create a GenieStackBuildingGroup if building.has_member("stack_unit_id") and \ building["stack_unit_id"].value > -1: - stack_building = True + stack_building_head = True if building.has_member("head_unit_id") and \ building["head_unit_id"].value > -1: - # we don't care about head units because we process - # them with their stack unit - continue + stack_building_annex = True # Check if the building is part of an existing line. # To do this, we look for connected techs and @@ -104,9 +106,21 @@ def create_building_lines(full_data_set: GenieObjectContainer) -> None: if line_id == building_id: # First building in line - if stack_building: + + # Check if the unit is only a building _part_, e.g. a + # head or an annex of a stack building + if stack_building_head: stack_unit_id = building["stack_unit_id"].value building_line = GenieStackBuildingGroup(stack_unit_id, line_id, full_data_set) + full_data_set.stack_building_groups.update({stack_unit_id: building_line}) + + elif stack_building_annex: + head_unit_id = building["head_unit_id"].value + head_building = full_data_set.genie_units[head_unit_id] + + stack_unit_id = head_building["stack_unit_id"].value + building_line = GenieStackBuildingGroup(stack_unit_id, head_unit_id, full_data_set) + full_data_set.stack_building_groups.update({stack_unit_id: building_line}) else: building_line = GenieBuildingLineGroup(line_id, full_data_set) diff --git a/openage/convert/processor/conversion/aoc/main/link/gather.py b/openage/convert/processor/conversion/aoc/main/link/gather.py index c8755c0789..a529fc1027 100644 --- a/openage/convert/processor/conversion/aoc/main/link/gather.py +++ b/openage/convert/processor/conversion/aoc/main/link/gather.py @@ -31,5 +31,15 @@ def link_gatherers_to_dropsites(full_data_set: GenieObjectContainer) -> None: drop_site_id = drop_site_member.value if drop_site_id > -1: - drop_site = full_data_set.building_lines[drop_site_id] + if drop_site_id in full_data_set.building_lines: + # the drop site is a normal building line + drop_site = full_data_set.building_lines[drop_site_id] + + else: + # the drop site is an annex or head of a stack building + for stack_building in full_data_set.stack_building_groups.values(): + if drop_site_id in stack_building.get_stack_annex_ids(): + drop_site = stack_building + break + drop_site.add_gatherer_id(unit_id) diff --git a/openage/convert/processor/conversion/swgbcc/main/groups/building_line.py b/openage/convert/processor/conversion/swgbcc/main/groups/building_line.py index 642f5c3119..770c642cf7 100644 --- a/openage/convert/processor/conversion/swgbcc/main/groups/building_line.py +++ b/openage/convert/processor/conversion/swgbcc/main/groups/building_line.py @@ -105,6 +105,7 @@ def create_building_lines(full_data_set: GenieObjectContainer) -> None: building["head_unit_id"].value > -1: head_unit_id = building["head_unit_id"].value building_line = SWGBStackBuildingGroup(building_id, head_unit_id, full_data_set) + full_data_set.stack_building_groups.update({building_id: building_line}) else: building_line = GenieBuildingLineGroup(building_id, full_data_set) diff --git a/openage/convert/value_object/read/media/datfile/unit.py b/openage/convert/value_object/read/media/datfile/unit.py index c77cd78ac2..5b8715fe68 100644 --- a/openage/convert/value_object/read/media/datfile/unit.py +++ b/openage/convert/value_object/read/media/datfile/unit.py @@ -259,7 +259,7 @@ def get_data_format_members( Return the members in this struct. """ data_format = [ - (SKIP, "unit_id", StorageType.ID_MEMBER, "int16_t"), + (READ_GEN, "unit_id", StorageType.ID_MEMBER, "int16_t"), (SKIP, "misplaced0", StorageType.FLOAT_MEMBER, "float"), (SKIP, "misplaced1", StorageType.FLOAT_MEMBER, "float"), ] @@ -1073,7 +1073,7 @@ def get_data_format_members( if game_version.edition.game_id not in ("ROR", "AOE1DE"): data_format.extend([ (SKIP, "can_burn", StorageType.BOOLEAN_MEMBER, "int8_t"), - (SKIP, "building_annex", StorageType.ARRAY_CONTAINER, SubdataMember( + (READ_GEN, "building_annex", StorageType.ARRAY_CONTAINER, SubdataMember( ref_type=BuildingAnnex, length=4 )), From 757037b6f98c46b7cf19ead5eeb28d162c856528 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 20 Jul 2025 16:03:54 +0200 Subject: [PATCH 163/163] convert: Add names for nyan objects in DE2:TTK. --- .../conversion/de2/internal_nyan_names.py | 179 ++++++++++++------ 1 file changed, 119 insertions(+), 60 deletions(-) diff --git a/openage/convert/value_object/conversion/de2/internal_nyan_names.py b/openage/convert/value_object/conversion/de2/internal_nyan_names.py index e4f3c995bd..7cd1e6a05c 100644 --- a/openage/convert/value_object/conversion/de2/internal_nyan_names.py +++ b/openage/convert/value_object/conversion/de2/internal_nyan_names.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=line-too-long @@ -72,35 +72,69 @@ 1805: ("EliteMonaspa", "elite_monaspa"), # BfG - 2101: ("BfGUnkown_2101", "bfg_unkown_2101"), - 2102: ("BfGUnkown_2102", "bfg_unkown_2102"), - 2104: ("BfGUnkown_2104", "bfg_unkown_2104"), - 2105: ("BfGUnkown_2105", "bfg_unkown_2105"), - 2107: ("BfGUnkown_2107", "bfg_unkown_2107"), - 2108: ("BfGUnkown_2108", "bfg_unkown_2108"), - 2110: ("BfGUnkown_2110", "bfg_unkown_2110"), - 2111: ("BfGUnkown_2111", "bfg_unkown_2111"), - 2123: ("BfGUnkown_2123", "bfg_unkown_2123"), - 2124: ("BfGUnkown_2124", "bfg_unkown_2124"), - 2125: ("BfGUnkown_2125", "bfg_unkown_2125"), - 2126: ("BfGUnkown_2126", "bfg_unkown_2126"), - 2127: ("BfGUnkown_2127", "bfg_unkown_2127"), - 2128: ("BfGUnkown_2128", "bfg_unkown_2128"), - 2129: ("BfGUnkown_2129", "bfg_unkown_2129"), - 2130: ("BfGUnkown_2130", "bfg_unkown_2130"), - 2131: ("BfGUnkown_2131", "bfg_unkown_2131"), - 2132: ("BfGUnkown_2132", "bfg_unkown_2132"), - 2133: ("BfGUnkown_2133", "bfg_unkown_2133"), - 2134: ("BfGUnkown_2134", "bfg_unkown_2134"), - 2135: ("BfGUnkown_2135", "bfg_unkown_2135"), - 2138: ("BfGUnkown_2138", "bfg_unkown_2138"), - 2139: ("BfGUnkown_2139", "bfg_unkown_2139"), - 2140: ("BfGUnkown_2140", "bfg_unkown_2140"), - 2148: ("BfGUnkown_2148", "bfg_unkown_2148"), - 2149: ("BfGUnkown_2149", "bfg_unkown_2149"), - 2150: ("BfGUnkown_2150", "bfg_unkown_2150"), - 2151: ("BfGUnkown_2151", "bfg_unkown_2151"), - 2162: ("BfGUnkown_2162", "bfg_unkown_2162"), + 2101: ("Immortal", "immortal"), + 2104: ("Strategos", "strategos"), + 2107: ("Hippeus", "hippeus"), + 2110: ("Hoplite", "hoplite"), + 2123: ("Lembos", "lembos"), + 2127: ("Monoreme", "monoreme"), + 2130: ("AntiqueGalley", "antique_galley"), + 2133: ("IncendiaryRaft", "incendiary_raft"), + 2138: ("CatapultShip", "catapult_ship"), + 2148: ("AntiqueTransportShip", "antique_transport_ship"), + 2149: ("MerchantShip", "merchant_ship"), + 2150: ("WarChariot", "war_chariot"), + 2162: ("Polemarch", "polemarch"), + + # TODO: These are upgrades + 2102: ("EliteImmortal", "elite_immortal"), + 2105: ("EliteStrategos", "elite_strategos"), + 2108: ("EliteHippeus", "elite_hippeus"), + 2111: ("EliteHoplite", "elite_hoplite"), + 2124: ("WarLembos", "war_lembos"), + 2125: ("HeavyLembos", "heavy_lembos"), + 2126: ("EliteLembos", "elite_lembos"), + 2128: ("Bireme", "bireme"), + 2129: ("Trieme", "trireme"), + 2131: ("AntiqueWarGalley", "antique_war_galley"), + 2132: ("AntiqueEliteGalley", "antique_elite_galley"), + 2134: ("IncendiaryShip", "incendiary_ship"), + 2135: ("HeavyIncendiaryShip", "heavy_incendiary_ship"), + 2139: ("OnagerShip", "onager_ship"), + 2140: ("Leviathan", "leviathan"), + 2151: ("EliteWarChariot", "elite_war_chariot"), + + # TTK + 1302: ("DragonShip", "dragon_ship"), + 1901: ("FireLancer", "fire_lancer"), + 1904: ("RocketCart", "rocket_cart"), + 1908: ("IronPagoda", "iron_pagoda"), + 1911: ("Grenadier", "grenadier"), + 1920: ("LiaoDao", "liao_dao"), + 1923: ("MountedTrebuchet", "mounted_trebuchet"), + 1942: ("TractionTrebuchet", "traction_trebuchet"), + 1944: ("HeiGuangCavalry", "hei_guang_cavalry"), + 1948: ("LouChuan", "lou_chuan"), + 1949: ("TigerCavalry", "tiger_cavalry"), + 1952: ("XianbeiRaider", "xianbei_raider"), + 1954: ("CaoCao", "cao_cao"), + 1959: ("WhiteFeatherGuard", "white_feather_guard"), + 1962: ("WarChariotFocusFire", "war_chariot_focus_fire"), + 1966: ("LiuBei", "liu_bei"), + 1968: ("FireArcher", "fire_archer"), + 1974: ("JianSwordsman", "jian_swordsman"), + 1978: ("SunJian", "sun_jian"), + 1980: ("WarChariotBarrage", "war_chariot_barrage"), + + # TODO: These are upgrades + 1903: ("EliteFireLancer", "elite_fire_lancer"), + 1907: ("HeavyRocketCart", "heavy_rocket_cart"), + 1910: ("EliteIronPagoda", "elite_iron_pagoda"), + 1922: ("EliteLiaoDao", "elite_liao_dao"), + 1946: ("HeavyHeiGuangCavalry", "heavy_hei_guang_cavalry"), + 1951: ("EliteTigerCavalry", "elite_tiger_cavalry"), + 1961: ("EliteWhiteFeatherGuard", "elite_white_feather_guard"), + 1970: ("EliteFireArcher", "elite_fire_archer"), } # key: head unit id; value: (nyan object name, filename prefix) @@ -121,8 +155,11 @@ 1808: ("MuleCart", "mule_cart"), # BfG - 2119: ("BfGUnkown_2119", "bfg_unkown_2119"), - 2172: ("BfGUnkown_2172", "bfg_unkown_2172"), + 2119: ("Shipyard", "shipyard"), + 2172: ("Port", "port"), + + # TTK + 1897: ("Pasture", "pasture"), } # key: (head) unit id; value: (nyan object name, filename prefix) @@ -208,31 +245,45 @@ 967: ("EliteQizilbashWarrior", "elite_qizilbash_warrior"), # BfG - 1110: ("BfGUnkown_1110", "bfg_unkown_1110"), - 1111: ("BfGUnkown_1111", "bfg_unkown_1111"), - 1112: ("BfGUnkown_1112", "bfg_unkown_1112"), - 1113: ("BfGUnkown_1113", "bfg_unkown_1113"), - 1120: ("BfGUnkown_1120", "bfg_unkown_1120"), - 1121: ("BfGUnkown_1121", "bfg_unkown_1121"), - 1122: ("BfGUnkown_1122", "bfg_unkown_1122"), - 1123: ("BfGUnkown_1123", "bfg_unkown_1123"), - 1130: ("BfGUnkown_1130", "bfg_unkown_1130"), - 1131: ("BfGUnkown_1131", "bfg_unkown_1131"), - 1132: ("BfGUnkown_1132", "bfg_unkown_1132"), - 1133: ("BfGUnkown_1133", "bfg_unkown_1133"), - 1161: ("BfGUnkown_1161", "bfg_unkown_1161"), - 1162: ("BfGUnkown_1162", "bfg_unkown_1162"), - 1165: ("BfGUnkown_1165", "bfg_unkown_1165"), - 1167: ("BfGUnkown_1167", "bfg_unkown_1167"), - 1173: ("BfGUnkown_1173", "bfg_unkown_1173"), - 1198: ("BfGUnkown_1198", "bfg_unkown_1198"), - 1202: ("BfGUnkown_1202", "bfg_unkown_1202"), - 1203: ("BfGUnkown_1203", "bfg_unkown_1203"), - 1204: ("BfGUnkown_1204", "bfg_unkown_1204"), - 1223: ("BfGUnkown_1223", "bfg_unkown_1223"), - 1224: ("BfGUnkown_1224", "bfg_unkown_1224"), - 1225: ("BfGUnkown_1225", "bfg_unkown_1225"), - 1226: ("BfGUnkown_1226", "bfg_unkown_1226"), + 1110: ("Sparabaras", "sparabaras"), + 1111: ("Satrapies", "satrapies"), + 1112: ("ScythedChariots", "scythed_chariots"), + 1113: ("Palta", "palta"), + 1120: ("Dekate", "dekate"), + 1121: ("Taxiarchs", "taxiarchs"), + 1122: ("Eisphora", "eisphora"), + 1123: ("MinesOfLaurion", "mines_of_laurion"), + 1130: ("HelotLevies", "helot_levies"), + 1131: ("Xyphos", "xyphos"), + 1132: ("Krypteia", "krypteia"), + 1133: ("PeloponnesianLeague", "peloponnesian_league"), + 1161: ("ScoopNets", "scoop_nets"), + 1162: ("Drums", "drums"), + 1165: ("Hypozomata", "hypozomata"), + 1167: ("Shipwright", "shipwright"), + 1173: ("TwoHandedSwordsman", "two_handed_swordsman"), + 1198: ("SpawnLembosFromPort", "spawn_lembos_from_port"), + 1202: ("Oligarchy", "oligarchy"), + 1203: ("Democracy", "democracy"), + 1204: ("Tyranny", "tyranny"), + 1223: ("Ephorate", "ephorate"), + 1224: ("Morai", "morai"), + 1225: ("Skeuophoroi", "skeuophoroi"), + 1226: ("Hippagretai", "hippagretai"), + + # TTK + 980: ("HeavyRocketCart", "heavy_rocket_cart"), + 982: ("EliteFireLancer", "elite_fire_lancer"), + 991: ("EliteIronPagoda", "elite_iron_pagoda"), + 996: ("FortifiedBastions", "fortified_bastions"), + 997: ("ThunderclapBombs", "thunderclap_bombs"), + 1002: ("EliteLiaoDao", "elite_liao_dao"), + 1006: ("LamellarArmor", "lamellar_armor"), + 1007: ("OrdoCavalry", "ordo_cavalry"), + 1010: ("DragonShip", "dragon_ship"), + 1012: ("Transhumance", "transhumance"), + 1013: ("Pastoralism", "pastoralism"), + 1014: ("Domestication", "domestication"), } # key: civ index; value: (nyan object name, filename prefix) @@ -265,15 +316,23 @@ 45: ("Georgians", "georgians"), # BfG - 46: ("BfGUnkown_46", "bfg_unkown_46"), - 47: ("BfGUnkown_47", "bfg_unkown_47"), - 48: ("BfGUnkown_48", "bfg_unkown_48"), + 46: ("Achaemenids", "achaemenids"), + 47: ("Athenians", "athenians"), + 48: ("Spartans", "spartans"), + + # TTK + 49: ("Shu", "shu"), + 50: ("Wu", "wu"), + 51: ("Wei", "wei"), + 52: ("Jurchens", "jurchens"), + 53: ("Khitans", "khitans"), } # key: civ index; value: (civ ids, nyan object name, filename prefix) # contains only new/changed graphic sets of DE2 GRAPHICS_SET_LOOKUPS = { 0: ((0, 1, 2, 13, 14, 36), "WesternEuropean", "western_european"), + 2: ((5, 6, 12, 18, 31, 49, 50, 51, 52, 53), "EastAsian", "east_asian"), 4: ((7, 37), "Byzantine", "byzantine"), 6: ((19, 24, 43, 44, 45, 46, 47, 48), "Mediterranean", "mediterranean"), 7: ((20, 40, 41, 42), "Indian", "indian"),