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/doc/code/curves.md b/doc/code/curves.md index e914f8bcfe..6473df2b6e 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 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 +time in `frame(t)`. diff --git a/doc/code/game_simulation/activity.md b/doc/code/game_simulation/activity.md index 0b84864fe9..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,14 +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 | +| 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 b511a626a9..e4a146d52c 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) @@ -50,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 @@ -88,8 +87,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 +105,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 @@ -121,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/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/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 de5c14201d..0996f38a8b 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.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 #include #include #include @@ -13,6 +14,7 @@ #include "log/log.h" #include "log/message.h" +#include "curve/concept.h" #include "curve/keyframe_container.h" #include "event/evententity.h" #include "time/time.h" @@ -26,7 +28,7 @@ class EventLoop; namespace curve { -template +template class BaseCurve : public event::EventEntity { public: BaseCurve(const std::shared_ptr &loop, @@ -74,30 +76,62 @@ class BaseCurve : public event::EventEntity { /** * Insert/overwrite given value at given time and erase all elements * that follow at a later time. + * * If multiple elements exist at the given time, * overwrite the last one. + * + * @param at Time the keyframe is inserted at. + * @param value Value of the keyframe. + * @param compress If true, only insert the keyframe if the value at time \p at + * is different from the given value. */ - virtual void set_last(const time::time_t &at, const T &value); + virtual void set_last(const time::time_t &at, + const T &value, + bool compress = false); /** * Insert a value at the given time. + * * If there already is a value at this time, * the value is inserted directly after the existing one. + * + * @param at Time the keyframe is inserted at. + * @param value Value of the keyframe. + * @param compress If true, only insert the keyframe if the value at time \p at + * is different from the given value. */ - virtual void set_insert(const time::time_t &at, const T &value); + virtual void set_insert(const time::time_t &at, + const T &value, + bool compress = false); /** * Insert a value at the given time. + * * If there already is a value at this time, * the given value will replace the first value with the same time. + * + * @param at Time the keyframe is inserted at. + * @param value Value of the keyframe. */ - virtual void set_replace(const time::time_t &at, const T &value); + virtual void set_replace(const time::time_t &at, + const T &value); /** * Remove all values that have the given time. */ virtual void erase(const time::time_t &at); + /** + * Compress the curve by removing redundant keyframes. + * + * A keyframe is redundant if it doesn't change the value calculation of the curve + * at any given time, e.g. duplicate keyframes. + * + * @param start Start time at which keyframes are compressed (default = -INF). + * Using the default value compresses ALL keyframes of the curve. + */ + virtual void compress(const time::time_t &start = time::TIME_MIN) = 0; + /** * Integrity check, for debugging/testing reasons only. */ @@ -113,9 +147,13 @@ class BaseCurve : public event::EventEntity { * @param start Start time at which keyframes are replaced (default = -INF). * Using the default value replaces ALL keyframes of \p this with * the keyframes of \p other. + * @param compress If true, redundant keyframes are not copied during the sync. + * Redundant keyframes are keyframes that don't change the value + * calculaton of the curve at any given time, e.g. duplicate keyframes. */ void sync(const BaseCurve &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 +168,15 @@ class BaseCurve : public event::EventEntity { * @param start Start time at which keyframes are replaced (default = -INF). * Using the default value replaces ALL keyframes of \p this with * the keyframes of \p other. + * @param compress If true, redundant keyframes are not copied during the sync. + * Redundant keyframes are keyframes that don't change the value + * calculaton of the curve at any given time, e.g. duplicate keyframes. */ - template + template void sync(const BaseCurve &other, const std::function &converter, - const time::time_t &start = time::TIME_MIN); + const time::time_t &start = time::TIME_MIN, + bool compress = false); /** * Get the identifier of this curve. @@ -199,8 +241,10 @@ class BaseCurve : public event::EventEntity { }; -template -void BaseCurve::set_last(const time::time_t &at, const T &value) { +template +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 @@ -210,6 +254,13 @@ void BaseCurve::set_last(const time::time_t &at, const T &value) { hint = this->container.erase_after(hint); + if (compress and this->get(at) == value) { + // skip insertion if the value is the same as the last one + // erasure still happened, so we need to notify about the change + this->changes(at); + return; + } + this->container.insert_before(at, value, hint); this->last_element = hint; @@ -217,32 +268,42 @@ 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) { +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) { +template +void BaseCurve::set_replace(const time::time_t &at, + const T &value) { this->container.insert_overwrite(at, value, this->last_element); this->changes(at); } -template +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); @@ -250,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++; @@ -258,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; @@ -270,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) { @@ -281,9 +342,10 @@ void BaseCurve::check_integrity() const { } } -template +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); @@ -294,15 +356,20 @@ void BaseCurve::sync(const BaseCurve &other, this->set_insert(start, get_other); } + if (compress) { + this->compress(start); + } + this->changes(start); } -template -template +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); @@ -313,6 +380,10 @@ void BaseCurve::sync(const BaseCurve &other, this->set_insert(start, get_other); } + if (compress) { + this->compress(start); + } + this->changes(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..510fe32bf0 100644 --- a/libopenage/curve/container/queue.h +++ b/libopenage/curve/container/queue.h @@ -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: /** @@ -69,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. @@ -95,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. */ @@ -242,7 +247,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 +271,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 +286,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 +312,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 +322,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 +338,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 +348,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 +363,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 +420,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); @@ -426,8 +431,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); } 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 0cf438f237..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; @@ -33,18 +34,24 @@ 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; }; -template -void Continuous::set_last(const time::time_t &at, const T &value) { +template +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 @@ -54,6 +61,13 @@ void Continuous::set_last(const time::time_t &at, const T &value) { hint = this->container.erase_after(hint); + if (compress and this->get(at) == value) { + // skip insertion if the value is the same as the last one + // erasure still happened, so we need to notify about the change + this->changes(at); + return; + } + this->container.insert_before(at, value, hint); this->last_element = hint; @@ -61,13 +75,15 @@ 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) { +template +void Continuous::set_insert(const time::time_t &t, + const T &value, + bool /* compress */) { this->set_replace(t, value); } -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 b9f9b6b00c..959ac36cd6 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 @@ -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; @@ -34,6 +30,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. */ @@ -51,15 +49,42 @@ 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 +void Discrete::compress(const time::time_t &start) { + 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(); ++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 +template std::string Discrete::idstr() const { std::stringstream ss; ss << "DiscreteCurve["; @@ -74,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; @@ -84,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 953939f975..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,20 +28,19 @@ 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; // 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; /** @@ -71,16 +71,20 @@ class DiscreteMod : public Discrete { }; -template -void DiscreteMod::set_last(const time::time_t &at, const T &value) { - BaseCurve::set_last(at, value); +template +void DiscreteMod::set_last(const time::time_t &at, + const T &value, + bool compress) { + BaseCurve::set_last(at, value, compress); this->time_length = at; } -template -void DiscreteMod::set_insert(const time::time_t &at, const T &value) { - BaseCurve::set_insert(at, value); +template +void DiscreteMod::set_insert(const time::time_t &at, + const T &value, + bool compress) { + BaseCurve::set_insert(at, value, compress); if (this->time_length < at) { this->time_length = at; @@ -88,7 +92,7 @@ void DiscreteMod::set_insert(const time::time_t &at, const T &value) { } -template +template void DiscreteMod::erase(const time::time_t &at) { BaseCurve::erase(at); @@ -98,7 +102,7 @@ void DiscreteMod::erase(const time::time_t &at) { } -template +template std::string DiscreteMod::idstr() const { std::stringstream ss; ss << "DiscreteRingCurve["; @@ -113,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) { @@ -126,8 +130,9 @@ 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 { +template +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 @@ -139,8 +144,9 @@ 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 { +template +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/interpolated.h b/libopenage/curve/interpolated.h index 564ba1d0af..6a4504527c 100644 --- a/libopenage/curve/interpolated.h +++ b/libopenage/curve/interpolated.h @@ -1,8 +1,9 @@ -// 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 #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; @@ -33,12 +34,31 @@ 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: + /** + * 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_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_frac) const; }; -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; @@ -46,36 +66,85 @@ 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 - || 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(); } else { - // Interpolation between time(now) and time(next) that has elapsed + // 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(); - // 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) { + // 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) { + // offset is between current keyframe and the last kept keyframe + 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) + 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 +inline T Interpolated::interpolate(typename KeyframeContainer::elem_ptr before, + typename KeyframeContainer::elem_ptr after, + double elapsed_frac) const { + ENSURE(before <= after, "Index of 'before' must be before '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_frac; + return this->container.get(before).val() + diff_value; +} + } // namespace openage::curve 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 8434942058..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 @@ -7,6 +7,7 @@ #include #include +#include "curve/concept.h" #include "curve/keyframe.h" #include "time/time.h" #include "util/fixed_point.h" @@ -15,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 +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. @@ -58,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. @@ -70,105 +69,188 @@ 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 a hint. + * Insert a new element. The search for the insertion point 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 you really + * do not have the possibility to get a hint. + * + * 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 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()); } /** - * 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 + * 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 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 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, const elem_ptr &hint); + elem_ptr insert_before(const keyframe_t &keyframe, + 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. 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 you really + * do not have the possibility to get a hint. + * + * 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 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. + * 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. + * + * @param time Time of the new keyframe. + * @param value Value of the new keyframe. + * @param hint Index of the approximate insertion location. + * + * @return 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 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 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); /** - * 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 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 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), @@ -177,10 +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 Location (index) of the inserted element. */ elem_ptr insert_overwrite(const time::time_t &time, const T &value, @@ -191,18 +282,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 keyframe Keyframe to insert. + * @param hint Index of the approximate insertion location. + * + * @return 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 &keyframe, + 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 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 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,39 +315,55 @@ 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 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); } /** - * 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) { @@ -250,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(); @@ -298,7 +419,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); @@ -327,37 +448,25 @@ 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 this->container.push_back(keyframe_t(time::TIME_MIN, T())); } -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 this->container.push_back(keyframe_t(time::TIME_MIN, defaultval)); } -template +template size_t KeyframeContainer::size() const { return this->container.size(); } -/* - * 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 +template typename KeyframeContainer::elem_ptr KeyframeContainer::last(const time::time_t &time, const KeyframeContainer::elem_ptr &hint) const { @@ -383,15 +492,7 @@ 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. - */ -template +template typename KeyframeContainer::elem_ptr KeyframeContainer::last_before(const time::time_t &time, const KeyframeContainer::elem_ptr &hint) const { @@ -417,10 +518,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) { @@ -443,10 +541,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, @@ -472,11 +567,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) { @@ -492,10 +583,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 @@ -509,10 +597,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); @@ -520,29 +605,29 @@ KeyframeContainer::erase(KeyframeContainer::elem_ptr e) { } -template +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(); } -template -template +template +template typename KeyframeContainer::elem_ptr KeyframeContainer::sync(const KeyframeContainer &other, const std::function &converter, @@ -551,20 +636,21 @@ 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(); } -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/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(); 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/gamestate/activity/CMakeLists.txt b/libopenage/gamestate/activity/CMakeLists.txt index 78a78e7ab0..408c88681e 100644 --- a/libopenage/gamestate/activity/CMakeLists.txt +++ b/libopenage/gamestate/activity/CMakeLists.txt @@ -9,7 +9,9 @@ add_sources(libopenage types.cpp xor_event_gate.cpp xor_gate.cpp + xor_switch_gate.cpp ) add_subdirectory("event") add_subdirectory("condition") +add_subdirectory("tests") diff --git a/libopenage/gamestate/activity/condition/CMakeLists.txt b/libopenage/gamestate/activity/condition/CMakeLists.txt index aabd159b0c..1e1ef2a811 100644 --- a/libopenage/gamestate/activity/condition/CMakeLists.txt +++ b/libopenage/gamestate/activity/condition/CMakeLists.txt @@ -1,4 +1,7 @@ add_sources(libopenage + ability_usable.cpp command_in_queue.cpp + next_command_switch.cpp next_command.cpp + target_in_range.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/command_in_queue.cpp b/libopenage/gamestate/activity/condition/command_in_queue.cpp index 7e300701f1..8f8db57c7d 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,11 +9,13 @@ 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 & /* state */, + const std::shared_ptr & /* api_object */) { 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/command_in_queue.h b/libopenage/gamestate/activity/condition/command_in_queue.h index 67c7a794cc..c55eec6b55 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,28 @@ #include "time/time.h" +namespace nyan { +class Object; +} + namespace openage::gamestate { class GameEntity; +class GameState; 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 & /* state */, + 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 c0b619a783..57ea9e20fa 100644 --- a/libopenage/gamestate/activity/condition/next_command.cpp +++ b/libopenage/gamestate/activity/condition/next_command.cpp @@ -1,37 +1,33 @@ -// 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" +#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) { +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)); - if (command_queue->get_queue().empty(time)) { + if (command_queue->get_commands().empty(time)) { return false; } - auto command = command_queue->get_queue().front(time); - return command->get_type() == component::command::command_t::MOVE; -} + auto queue_command = command_queue->get_commands().front(time); -bool next_command_move(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 compare_command = condition->get("NextCommand.command"); + auto compare_type = api::COMMAND_LOOKUP.get(compare_command->get_name()); - auto command = command_queue->get_queue().front(time); - return command->get_type() == component::command::command_t::MOVE; + 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 046a18cec6..a1958a9cfe 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-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,32 +8,31 @@ #include "time/time.h" +namespace nyan { +class Object; +} + namespace openage::gamestate { class GameEntity; +class GameState; namespace activity { /** - * Condition for next command check in the activity system. - * - * @param time Time when the condition is checked. - * @param entity Game entity. + * Check whether the next command in the entity command queue is of a specific type. * - * @return true if the entity has a idle command next in the queue, false otherwise. - */ -bool next_command_idle(const time::time_t &time, - const std::shared_ptr &entity); - -/** - * Condition for next command check in the activity system. + * The command type is parsed from the nyan object \p condition. * * @param time Time when the condition is checked. - * @param entity Game entity. + * @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 a move command next in the queue, false otherwise. + * @return true if the entity has the command next in the queue, false otherwise. */ -bool next_command_move(const time::time_t &time, - const std::shared_ptr &entity); +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 } // namespace openage::gamestate 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..33626c4338 --- /dev/null +++ b/libopenage/gamestate/activity/condition/next_command_switch.cpp @@ -0,0 +1,27 @@ +// Copyright 2024-2025 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, + 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)); + + if (command_queue->get_commands().empty(time)) { + return -1; + } + + auto command = command_queue->get_commands().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..3e49d71c79 --- /dev/null +++ b/libopenage/gamestate/activity/condition/next_command_switch.h @@ -0,0 +1,38 @@ +// Copyright 2024-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 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 & /* state */, + const std::shared_ptr & /* api_object */); + +} // namespace activity +} // namespace openage::gamestate 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..ca95650084 --- /dev/null +++ b/libopenage/gamestate/activity/condition/target_in_range.cpp @@ -0,0 +1,106 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#include "target_in_range.h" + +#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" +#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) { + 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)); + auto target = command_queue->get_target(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; + } + + // 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(used_abilty_obj, api::ability_property_t::RANGED)) { + // Get min/max range from the property of the ability + 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( + 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 + 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()); + 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 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); +} + +} // 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..a40c195637 --- /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 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 &condition); + +} // namespace activity +} // namespace openage::gamestate 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/activity/tests.cpp b/libopenage/gamestate/activity/tests.cpp index 1ddad0e7e8..ee9b193b6c 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, nullptr, condition_obj)) { next_id = condition.first; break; } @@ -233,7 +234,9 @@ 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 & /* state */, + const std::shared_ptr & /* api_object */) { log::log(INFO << "Checking condition (counter < 4): counter=" << counter); if (counter < 4) { log::log(INFO << "Selecting path 1 (back to task node " << task1->get_id() << ")"); @@ -243,14 +246,20 @@ 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 & /* state */, + const std::shared_ptr & /* api_object */) { // No check needed here, the event node is always selected log::log(INFO << "Selecting path 2 (to event node " << event_node->get_id() << ")"); return true; }; - 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/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..bdd90ec2b4 --- /dev/null +++ b/libopenage/gamestate/activity/tests/node_types.cpp @@ -0,0 +1,272 @@ +// Copyright 2024-2025 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::IDLE; + 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, + {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; + }}); + + auto option2_node = std::make_shared(3); + xor_gate_node->add_output(option2_node, + {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; + }}); + + auto conditions = xor_gate_node->get_conditions(); + + // Check the conditions + TESTEQUALS(conditions.size(), 2); + + // Check if the conditions are set correctly + // 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, 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); + 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 */, + const std::shared_ptr & /* state */, + const std::shared_ptr & /* api_object */) { + if (time == time::TIME_ZERO) { + return 1; + } + if (time == time::TIME_MAX) { + return 2; + } + + return 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 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, 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(); + + // 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/libopenage/gamestate/activity/types.h b/libopenage/gamestate/activity/types.h index ac2189e5ab..41f24dc857 100644 --- a/libopenage/gamestate/activity/types.h +++ b/libopenage/gamestate/activity/types.h @@ -1,9 +1,29 @@ -// 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 +#include -namespace openage::gamestate::activity { +#include "time/time.h" + + +namespace nyan { +class Object; +} // namespace nyan + +namespace openage { + +namespace event { +class Event; +class EventLoop; +} // namespace event + +namespace gamestate { +class GameEntity; +class GameState; + +namespace activity { /** * Node types in the flow graph. @@ -13,8 +33,91 @@ enum class node_t { END, XOR_EVENT_GATE, XOR_GATE, + XOR_SWITCH_GATE, TASK_CUSTOM, 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 &time, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, + 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. + * @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 &entity, + const std::shared_ptr &state, + 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. + */ +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. + * @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 &entity, + const std::shared_ptr &state, + 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 +} // namespace openage diff --git a/libopenage/gamestate/activity/xor_event_gate.h b/libopenage/gamestate/activity/xor_event_gate.h index 38c8ccacb1..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. @@ -61,7 +28,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. @@ -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.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 f73287ed86..89b0816a29 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 game 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. @@ -59,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; @@ -72,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. @@ -103,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. @@ -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 new file mode 100644 index 0000000000..70658d3e5c --- /dev/null +++ b/libopenage/gamestate/activity/xor_switch_gate.cpp @@ -0,0 +1,53 @@ +// Copyright 2024-2025 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 switch_condition &switch_func, + const lookup_dict_t &lookup_dict, + const std::shared_ptr &default_node) : + Node{id, label}, + switch_func{switch_func}, + lookup_dict{lookup_dict}, + default_node{default_node} {} + +void XorSwitchGate::set_output(const std::shared_ptr &output, + const switch_key_t &key) { + this->outputs.emplace(output->get_id(), output); + this->lookup_dict.emplace(key, output); +} + +const switch_condition &XorSwitchGate::get_switch_func() const { + return this->switch_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 { + 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..a4e32beb42 --- /dev/null +++ b/libopenage/gamestate/activity/xor_switch_gate.h @@ -0,0 +1,132 @@ +// Copyright 2024-2025 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::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: + /** + * 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 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_condition &switch_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 switch key. + * + * @param output Output node. + * @param key Enumeration value. + */ + void set_output(const std::shared_ptr &output, + const switch_key_t &key); + + /** + * Get the switch function for determining the output nodes. + * + * @return Switch function. + */ + const switch_condition &get_switch_func() const; + + /** + * Set the switch function for determining the output nodes. + * + * @param switch_func Switch function. + */ + void set_switch_func(const switch_condition &switch_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. + */ + switch_condition switch_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 openage::gamestate::activity diff --git a/libopenage/gamestate/api/CMakeLists.txt b/libopenage/gamestate/api/CMakeLists.txt index f3ad8d7b40..db07762baf 100644 --- a/libopenage/gamestate/api/CMakeLists.txt +++ b/libopenage/gamestate/api/CMakeLists.txt @@ -3,9 +3,12 @@ add_sources(libopenage activity.cpp animation.cpp definitions.cpp + effect.cpp + object.cpp patch.cpp player_setup.cpp property.cpp + resistance.cpp sound.cpp terrain.cpp types.cpp diff --git a/libopenage/gamestate/api/ability.cpp b/libopenage/gamestate/api/ability.cpp index 49ecfad48f..a45d8e3afc 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-2025 the openage authors. See copying.md for legal info. #include "ability.h" @@ -11,24 +11,54 @@ #include "datastructure/constexpr_map.h" #include "gamestate/api/definitions.h" +#include "gamestate/api/util.h" 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, + const ability_t &type) { + 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() == api_parent; +} + +ability_t APIAbility::get_type(const nyan::Object &ability) { + nyan::fqon_t api_parent = get_api_parent(ability); + + // TODO: remove once other ability types are implemented + if (not ABILITY_TYPE_LOOKUP.contains(api_parent)) { + return ability_t::UNKNOWN; + } + + return ABILITY_TYPE_LOOKUP.get(api_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); - 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..f58974385e 100644 --- a/libopenage/gamestate/api/ability.h +++ b/libopenage/gamestate/api/ability.h @@ -1,10 +1,12 @@ -// 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 "gamestate/api/types.h" +#include "gamestate/component/types.h" + namespace openage::gamestate::api { @@ -22,6 +24,35 @@ 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); + + /** + * 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); + + /** + * 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/activity.cpp b/libopenage/gamestate/api/activity.cpp index edd596abe8..535cfea1ca 100644 --- a/libopenage/gamestate/api/activity.cpp +++ b/libopenage/gamestate/api/activity.cpp @@ -1,15 +1,21 @@ -// 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 "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_DEFS.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) { @@ -38,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())}; } @@ -78,38 +103,122 @@ 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 = get_api_parent(switch_condition_obj); + auto switch_condition_type = ACTIVITY_SWITCH_CONDITION_TYPE_LOOKUP.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."); } } -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_DEFS.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_DEFS.get(ability->get_name()); + return ACTIVITY_TASK_SYSTEM_LOOKUP.get(task_obj_fqon); } 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_CONDITIONS.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 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 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 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: { + 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_LOOKUP.get(key_obj.get_name())); + + // Get node ID + auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); + auto next_node_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"; + 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) { - 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/activity.h b/libopenage/gamestate/api/activity.h index 2001aa7548..f0e1f93475 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::switch_function_t get_switch_func(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/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/definitions.h b/libopenage/gamestate/api/definitions.h index f982571f94..0566ab8e7a 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-2025 the openage authors. See copying.md for legal info. #pragma once @@ -8,14 +8,20 @@ #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" +#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" #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/component/internal/commands/types.h" +#include "gamestate/component/types.h" #include "gamestate/system/types.h" @@ -25,17 +31,206 @@ 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::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::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")))); /** - * Maps internal property types to nyan API values. + * 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 ability types to component types. + */ +static const auto COMPONENT_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. + */ +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 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. + */ +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.convert.type.AoE2Convert", // TODO: Remove from API + effect_t::DISCRETE_CONVERT_AOE2), + 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 API resistance types to internal effect types. + */ +static const auto RESISTANCE_TYPE_LOOKUP = datastructure::create_const_map( + std::pair("engine.resistance.continuous.flat_attribute_change.type.FlatAttributeChangeDecrease", + effect_t::CONTINUOUS_FLAC_DECREASE), + std::pair("engine.resistance.continuous.flat_attribute_change.type.FlatAttributeChangeIncrease", + effect_t::CONTINUOUS_FLAC_INCREASE), + std::pair("engine.resistance.continuous.type.Lure", + effect_t::CONTINUOUS_LURE), + std::pair("engine.resistance.continuous.type.TimeRelativeAttributeChangeDecrease", + effect_t::CONTINUOUS_TRAC_DECREASE), + std::pair("engine.resistance.continuous.type.TimeRelativeAttributeChangeIncrease", + effect_t::CONTINUOUS_TRAC_INCREASE), + std::pair("engine.resistance.continuous.type.TimeRelativeProgressChangeDecrease", + effect_t::CONTINUOUS_TRPC_DECREASE), + std::pair("engine.resistance.continuous.type.TimeRelativeProgressChangeIncrease", + effect_t::CONTINUOUS_TRPC_INCREASE), + std::pair("engine.resistance.discrete.type.Convert", + effect_t::DISCRETE_CONVERT), + 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.discrete.flat_attribute_change.type.FlatAttributeChangeIncrease", + effect_t::DISCRETE_FLAC_INCREASE), + std::pair("engine.resistance.discrete.type.MakeHarvestable", + effect_t::DISCRETE_MAKE_HARVESTABLE), + std::pair("engine.resistance.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, @@ -49,46 +244,87 @@ 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. + */ +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 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. */ -static const auto ACTIVITY_NODE_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", activity::node_t::END), std::pair("engine.util.activity.node.type.Ability", activity::node_t::TASK_SYSTEM), + std::pair("engine.util.activity.node.type.Task", + activity::node_t::TASK_SYSTEM), // TODO: Should this have its own type? std::pair("engine.util.activity.node.type.XORGate", activity::node_t::XOR_GATE), std::pair("engine.util.activity.node.type.XOREventGate", - activity::node_t::XOR_EVENT_GATE)); + activity::node_t::XOR_EVENT_GATE), + std::pair("engine.util.activity.node.type.XORSwitchGate", + activity::node_t::XOR_SWITCH_GATE)); /** * Maps API activity task system types to engine system types. * * TODO: Expand this to include all systems. */ -static const auto ACTIVITY_TASK_SYSTEM_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", system::system_id_t::IDLE), std::pair("engine.ability.type.Move", - system::system_id_t::MOVE_COMMAND)); + system::system_id_t::MOVE_COMMAND), + 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), + std::pair("engine.util.activity.task.type.MoveToTarget", + system::system_id_t::MOVE_TARGET)); /** * Maps API activity condition types to engine condition types. */ -static const auto ACTIVITY_CONDITIONS = 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.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)), + std::pair("engine.util.activity.condition.type.TargetInRange", + std::function(gamestate::activity::target_in_range)), + std::pair("engine.util.activity.condition.type.AbilityUsable", + std::function(gamestate::activity::component_enabled))); -static const auto ACTIVITY_EVENT_PRIMERS = datastructure::create_const_map( +/** + * Maps API activity event types to event primer functions. + */ +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", @@ -96,6 +332,22 @@ 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))); + +/** + * Maps API activity switch condition types to nyan API values. + */ +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)); + /** * Maps internal patch property types to nyan API values. */ @@ -103,4 +355,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_LOOKUP = 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/api/effect.cpp b/libopenage/gamestate/api/effect.cpp new file mode 100644 index 0000000000..e5b566fe7a --- /dev/null +++ b/libopenage/gamestate/api/effect.cpp @@ -0,0 +1,58 @@ +// 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 { + +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 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() == api_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 api_parent = get_api_parent(effect); + + return EFFECT_TYPE_LOOKUP.get(api_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/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 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 new file mode 100644 index 0000000000..b892856dfb --- /dev/null +++ b/libopenage/gamestate/api/resistance.cpp @@ -0,0 +1,58 @@ +// 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 { + +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 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() == api_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 api_parent = get_api_parent(resistance); + + return RESISTANCE_TYPE_LOOKUP.get(api_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/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/types.h b/libopenage/gamestate/api/types.h index 2c44fe1e81..35b036dd69 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-2025 the openage authors. See copying.md for legal info. #pragma once @@ -9,12 +9,41 @@ namespace openage::gamestate::api { * Types of abilities for API objects. */ enum class ability_t { + ACTIVITY, + APPLY_CONTINUOUS_EFFECT, + APPLY_DISCRETE_EFFECT, IDLE, + LINE_OF_SIGHT, LIVE, MOVE, + RESISTANCE, + SELECTABLE, TURN, - // TODO + // TODO: other ability types + + // TODO: remove once other ability types are implemented + UNKNOWN, +}; + +/** + * 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_CONVERT_AOE2, // TODO: Remove from API + DISCRETE_FLAC_DECREASE, + DISCRETE_FLAC_INCREASE, + DISCRETE_MAKE_HARVESTABLE, + DISCRETE_SEND_TO_CONTAINER, }; /** @@ -27,6 +56,25 @@ enum class ability_property_t { EXECUTION_SOUND, DIPLOMATIC, LOCK, + RANGED, +}; + +/** + * Types of properties for API effects. + */ +enum class effect_property_t { + AREA, + COST, + DIPLOMATIC, + PRIORITY, +}; + +/** + * Types of properties for API resistances. + */ +enum class resistance_property_t { + COST, + STACKED, }; /** @@ -36,4 +84,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/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/component/api/CMakeLists.txt b/libopenage/gamestate/component/api/CMakeLists.txt index 8588909bf2..dcd5c9ba2f 100644 --- a/libopenage/gamestate/component/api/CMakeLists.txt +++ b/libopenage/gamestate/component/api/CMakeLists.txt @@ -1,7 +1,10 @@ add_sources(libopenage + apply_effect.cpp idle.cpp + line_of_sight.cpp live.cpp move.cpp + resistance.cpp selectable.cpp turn.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..c9a709d3ea --- /dev/null +++ b/libopenage/gamestate/component/api/apply_effect.h @@ -0,0 +1,108 @@ +// Copyright 2024-2025 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 { + +/** + * 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); + + component_t get_type() const override; + + /** + * Get the last time an effect application was initiated that is before the given \p 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. + */ + const curve::Discrete &get_init_time() const; + + /** + * Get the last time the effects were applied before the given \p 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 initiates an effect application, + * i.e. it starts 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 applies the effects + * of the ability, i.e. init time + application delay. + * + * @param time Time at which the entity applies 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/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.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..373ed039e5 --- /dev/null +++ b/libopenage/gamestate/component/api/line_of_sight.h @@ -0,0 +1,21 @@ +// Copyright 2024-2025 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 { + +/** + * Stores the line of sight information of a game entity. + */ +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/api/live.cpp b/libopenage/gamestate/component/api/live.cpp index f7a1f17d98..e9194343ec 100644 --- a/libopenage/gamestate/component/api/live.cpp +++ b/libopenage/gamestate/component/api/live.cpp @@ -2,11 +2,9 @@ #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,34 @@ 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); +} + +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, - 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..6be45a8bfc 100644 --- a/libopenage/gamestate/component/api/live.h +++ b/libopenage/gamestate/component/api/live.h @@ -13,7 +13,20 @@ #include "time/time.h" -namespace openage::gamestate::component { +namespace openage { + +namespace curve { +template +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; @@ -29,7 +42,18 @@ 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); + + /** + * 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. @@ -40,16 +64,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/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.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..c73468534d --- /dev/null +++ b/libopenage/gamestate/component/api/resistance.h @@ -0,0 +1,24 @@ +// Copyright 2024-2025 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 { + +/** + * 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; + + component_t get_type() const override; +}; + +} // namespace openage::gamestate::component 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.cpp b/libopenage/gamestate/component/internal/command_queue.cpp index 4c6963a75d..d05e2ea216 100644 --- a/libopenage/gamestate/component/internal/command_queue.cpp +++ b/libopenage/gamestate/component/internal/command_queue.cpp @@ -1,9 +1,11 @@ -// 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" #include +#include "gamestate/component/internal/commands/apply_effect.h" +#include "gamestate/component/internal/commands/move.h" #include "gamestate/component/types.h" @@ -22,13 +24,52 @@ void CommandQueue::add_command(const time::time_t &time, this->command_queue.insert(time, command); } -curve::Queue> &CommandQueue::get_queue() { +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; } -const std::shared_ptr CommandQueue::pop_command(const time::time_t &time) { +void CommandQueue::clear(const time::time_t &time) { + this->command_queue.clear(time); +} + +const std::shared_ptr CommandQueue::pop(const time::time_t &time) { + if (this->command_queue.empty(time)) { + return nullptr; + } + return this->command_queue.pop_front(time); } +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); +} + +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 fb3179b470..45a9ec39d1 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" @@ -19,6 +23,9 @@ class EventLoop; namespace gamestate::component { +/** + * Stores commands for a game entity. + */ class CommandQueue final : public InternalComponent { public: /** @@ -31,33 +38,83 @@ 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. * * @return Command queue. */ - curve::Queue> &get_queue(); + const curve::Queue> &get_commands(); + + /** + * Clear all commands in the queue. + * + * @param time Time at which the queue is cleared. + */ + void clear(const time::time_t &time); /** - * Get the command in the front of the queue. + * Get the command in the front of the queue and remove it. + * + * Unlike curve::Queue::front(), calling this on an empty queue is + * not undefined behavior. + * + * @param time Time at which the command is popped. + * + * @return Command in the front of the queue or nullptr if the queue is empty. + */ + const std::shared_ptr pop(const time::time_t &time); + + /** + * get the command at the front of 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. */ - const std::shared_ptr pop_command(const time::time_t &time); + const std::shared_ptr front(const time::time_t &time) const; + + /** + * 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 target of the entity at the given time. + * + * The target may be empty if the command queue is empty or if the command + * has no target. + * + * @return Target of the entity. + */ + optional_target_t get_target(const time::time_t &time) const; private: /** * Command queue. + * + * Stores the commands received by the entity over time. */ curve::Queue> command_queue; }; 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/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/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 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: /** diff --git a/libopenage/gamestate/component/types.h b/libopenage/gamestate/component/types.h index 5e87b8ed23..0e117c7471 100644 --- a/libopenage/gamestate/component/types.h +++ b/libopenage/gamestate/component/types.h @@ -1,10 +1,17 @@ -// 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 +#include "util/fixed_point.h" + namespace openage::gamestate::component { +/** + * Type for attribute values. + */ +using attribute_value_t = util::FixedPoint; + /** * Types of components. */ @@ -16,11 +23,14 @@ enum class component_t { ACTIVITY, // API + APPLY_EFFECT, + RESISTANCE, IDLE, TURN, MOVE, SELECTABLE, - LIVE + LIVE, + LINE_OF_SIGHT, }; } // namespace openage::gamestate::component 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.cpp b/libopenage/gamestate/entity_factory.cpp index 6e1a4c825f..dbde0d5193 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -22,10 +22,16 @@ #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/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" #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" @@ -75,16 +81,20 @@ 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 & /* state */, + 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); @@ -106,8 +116,6 @@ std::shared_ptr create_test_activity() { } EntityFactory::EntityFactory() : - next_entity_id{0}, - next_player_id{0}, render_factory{nullptr} { } @@ -121,7 +129,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()); @@ -168,20 +176,25 @@ 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]; - if (ability_parent == "engine.ability.type.Move") { + 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: { 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); @@ -194,19 +207,44 @@ 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)); } + break; } - else if (ability_parent == "engine.ability.type.Activity") { + case api::ability_t::ACTIVITY: { activity_ability = ability_obj; + break; } - else if (ability_parent == "engine.ability.type.Selectable") { + case api::ability_t::SELECTABLE: { auto selectable = std::make_shared(loop, ability_obj); entity->add_component(selectable); + break; + } + 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; + } + case api::ability_t::RESISTANCE: { + auto resistance = std::make_shared(loop, ability_obj); + entity->add_component(resistance); + break; + } + case api::ability_t::LINE_OF_SIGHT: { + auto line_of_sight = std::make_shared(loop, ability_obj); + entity->add_component(line_of_sight); + break; + } + default: + // TODO: Change verbosity from SPAM to INFO once we cover all ability types + log::log(SPAM << "Entity has unrecognized ability type: " << ability_parent); } } @@ -273,6 +311,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()}; } @@ -303,7 +344,20 @@ void EntityFactory::init_activity(const std::shared_ptr(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); @@ -314,13 +368,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(); @@ -346,6 +402,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_ptr(switch_value->get_name()); + + 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); + 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}; } diff --git a/libopenage/gamestate/entity_factory.h b/libopenage/gamestate/entity_factory.h index 11efbe3f2e..f3f4e5ab1c 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 @@ -7,6 +7,7 @@ #include +#include "gamestate/definitions.h" #include "gamestate/types.h" @@ -119,12 +120,12 @@ class EntityFactory { /** * ID of the next game entity to be created. */ - entity_id_t next_entity_id; + entity_id_t next_entity_id = GAME_ENTITY_ID_START; /** * ID of the next player to be created. */ - player_id_t next_player_id; + player_id_t next_player_id = PLAYER_ID_START; /** * 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/send_command.cpp b/libopenage/gamestate/event/send_command.cpp index 8530d6340d..1f5cbc2d16 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-2025 the openage authors. See copying.md for legal info. #include "send_command.h" @@ -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" @@ -19,8 +20,8 @@ namespace component { class CommandQueue; namespace command { -class IdleCommand; -class MoveCommand; +class Idle; +class Move; } // namespace command } // namespace component @@ -64,16 +65,20 @@ void SendCommandHandler::invoke(openage::event::EventLoop & /* loop */, entity->get_component(component::component_t::COMMANDQUEUE)); switch (command_type) { - case component::command::command_t::IDLE: - command_queue->add_command(time, std::make_shared()); + case component::command::command_t::IDLE: { + command_queue->set_command(time, std::make_shared()); 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->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->set_command(time, std::make_shared(target_id)); break; + } default: break; } diff --git a/libopenage/gamestate/event/spawn_entity.cpp b/libopenage/gamestate/event/spawn_entity.cpp index 66f6df9be7..94bd9f8a7e 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" @@ -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" @@ -192,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 @@ -209,6 +210,8 @@ void SpawnEntityHandler::invoke(openage::event::EventLoop & /* loop */, 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); 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. */ diff --git a/libopenage/gamestate/system/CMakeLists.txt b/libopenage/gamestate/system/CMakeLists.txt index ec2ea97945..57a3ef3591 100644 --- a/libopenage/gamestate/system/CMakeLists.txt +++ b/libopenage/gamestate/system/CMakeLists.txt @@ -1,6 +1,9 @@ add_sources(libopenage activity.cpp + apply_effect.cpp + command_queue.cpp idle.cpp move.cpp + property.cpp types.cpp ) diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index 7ef1d574b4..33b7982b3b 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" @@ -16,9 +16,12 @@ #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" +#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" @@ -87,8 +90,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, state, condition_obj)) { next_id = condition.first; break; } @@ -111,6 +115,18 @@ 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 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, state, switch_condition_obj); + 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()}; } @@ -125,15 +141,23 @@ 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_command(entity, state, start_time); + break; case system_id_t::IDLE: return Idle::idle(entity, start_time); break; case system_id_t::MOVE_COMMAND: return Move::move_command(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); + case system_id_t::MOVE_TARGET: + return Move::move_target(entity, state, 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/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.cpp b/libopenage/gamestate/system/apply_effect.cpp new file mode 100644 index 0000000000..1cebbbc7e8 --- /dev/null +++ b/libopenage/gamestate/system/apply_effect.cpp @@ -0,0 +1,241 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#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/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" +#include "gamestate/system/property.h" + + +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(start_time)); + + if (not command) [[unlikely]] { + log::log(MSG(warn) << "Command is not a apply effect 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 */, + 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(); + auto batches = effect_ability.get_set("ApplyDiscreteEffect.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 (not 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 (not resistances.contains(resistance_type)) { + resistances.emplace(resistance_type, std::vector{}); + } + + resistances[resistance_type].push_back(resistance_obj); + } + + // application time + // 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"); + total_time += delay + reload_time; + + // Check for matching effects and resistances + for (auto &effect : effects) { + auto effect_type = effect.first; + auto effect_objs = effect.second; + + if (not resistances.contains(effect_type)) { + continue; + } + + auto resistance_objs = resistances[effect_type]; + + switch (effect_type) { + case api::effect_t::DISCRETE_FLAC_DECREASE: + [[fallthrough]]; + 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); + + if (effect_type == api::effect_t::DISCRETE_FLAC_DECREASE) { + // negate the applied value for decrease effects + applied_value = -applied_value; + } + + // Record the time when the effects were applied + effects_component->set_init_time(start_time + delay); + effects_component->set_last_used(start_time + total_time); + + // 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)); + } + } + + // properties + handle_animated(effector, effect_ability, start_time); + + return total_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.change_value"); + auto min_change_amount = effect.get_optional("FlatAttributeChange.min_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 + 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.block_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 new file mode 100644 index 0000000000..64c6aff15b --- /dev/null +++ b/libopenage/gamestate/system/apply_effect.h @@ -0,0 +1,86 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include + +#include "gamestate/component/types.h" +#include "time/time.h" + + +namespace openage { + +namespace gamestate { +class GameEntity; +class GameState; + +namespace system { + + +class ApplyEffect { +public: + /** + * 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. + * @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); + +private: + /** + * 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. + * @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 &effector, + const std::shared_ptr &state, + const std::shared_ptr &resistor, + const time::time_t &start_time); + + /** + * 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 +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/system/command_queue.cpp b/libopenage/gamestate/system/command_queue.cpp new file mode 100644 index 0000000000..4879f0b603 --- /dev/null +++ b/libopenage/gamestate/system/command_queue.cpp @@ -0,0 +1,32 @@ +// 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)); + + command_queue->clear(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)); + + command_queue->pop(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/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/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.cpp b/libopenage/gamestate/system/move.cpp index 5a44baf113..7229305ae2 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" @@ -80,8 +81,8 @@ 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)); + auto command = std::dynamic_pointer_cast( + command_queue->pop(start_time)); if (not command) [[unlikely]] { log::log(MSG(warn) << "Command is not a move command."); @@ -91,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, @@ -173,15 +206,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/move.h b/libopenage/gamestate/system/move.h index 346c1c0aa6..c3b5bf445e 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,11 @@ 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. * @param state Game state. * @param start_time Start time of change. @@ -29,9 +34,36 @@ 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. + * + * 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. + * + * @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); + +private: /** * 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. 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 diff --git a/libopenage/gamestate/system/types.h b/libopenage/gamestate/system/types.h index 1da20da895..5be3385e8a 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-2025 the openage authors. See copying.md for legal info. #pragma once @@ -11,12 +11,19 @@ namespace openage::gamestate::system { enum class system_id_t { NONE, + // ability systems + APPLY_EFFECT, + IDLE, MOVE_COMMAND, - MOVE_DEFAULT, + MOVE_TARGET, ACTIVITY_ADVANCE, + + // tasks + CLEAR_COMMAND_QUEUE, + POP_COMMAND_QUEUE, }; } // namespace openage::gamestate::system diff --git a/libopenage/input/controller/game/controller.cpp b/libopenage/input/controller/game/controller.cpp index 96ccf53429..7f3429f02e 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" @@ -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" @@ -23,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()) { @@ -37,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; @@ -92,6 +93,14 @@ 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; +} + void Controller::set_drag_select_start(const coord::input &start) { std::unique_lock lock{this->mutex}; @@ -146,14 +155,30 @@ 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) { - 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()}, - }; + binding_func_t interact_entity{[&](const event_arguments &args, + const std::shared_ptr controller) { + auto id_texture = controller->get_id_texture(); + auto texture_data = id_texture->into_data(); + + event::EventHandler::param_map::map_t params{}; + + 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); + 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", @@ -164,7 +189,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..a6740030e2 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,8 +40,14 @@ class BindingContext; */ class Controller : public std::enable_shared_from_this { public: - Controller(const std::unordered_set &controlled_factions, - size_t active_faction_id); + /** + * 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, + gamestate::player_id_t active_faction_id); ~Controller() = default; @@ -47,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. @@ -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. * @@ -103,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. @@ -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/input/input_manager.cpp b/libopenage/input/input_manager.cpp index 4857863eb1..cdcdae600e 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" diff --git a/libopenage/input/input_manager.h b/libopenage/input/input_manager.h index 25b6315341..76d7ac1c4c 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,9 @@ namespace qtgui { class GuiInput; } -namespace openage::input { +namespace openage { + +namespace input { namespace camera { class Controller; @@ -149,7 +151,6 @@ class InputManager { */ bool process(const QEvent &ev); - private: /** * Process the (default) action for an input event. @@ -222,4 +223,5 @@ class InputManager { */ void setup_defaults(const std::shared_ptr &ctx); -} // namespace openage::input +} // namespace input +} // namespace openage 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; }; diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index 9775b62d9b..0cd3122478 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{1, 2, 3, 4}; + gamestate::player_id_t active_faction_id = 1; 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/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."); 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"); } 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/opengl/texture.cpp b/libopenage/renderer/opengl/texture.cpp index 5e795bcd82..6a9d8d0538 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,56 @@ 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); + + // TODO: copy the old texture data into the new texture + + 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_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}; 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; }; 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/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/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/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/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/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/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/object.cpp b/libopenage/renderer/stages/world/object.cpp index 2396c94ed2..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()); + + // 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,16 +83,16 @@ void WorldObject::fetch_updates(const time::time_t &time) { } return this->asset_manager->request_animation(path); }), - this->last_update); - this->angle.sync(this->render_entity->get_angle(), this->last_update); - - // Unlock mutex of the render entity - read_lock.unlock(); + sync_time, + true); + 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(); diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index eec13b602b..147706d8a5 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -109,14 +109,18 @@ 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->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); } +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) { @@ -141,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); } 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. diff --git a/libopenage/renderer/texture.h b/libopenage/renderer/texture.h index c2abdab2d2..3789fb4627 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,61 @@ 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. + /** + * 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. + * + * @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; }; 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. */ diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index c751238cdf..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)); } @@ -422,6 +435,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); } @@ -518,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()); } @@ -526,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); } @@ -534,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()); } @@ -542,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); } @@ -551,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); } @@ -573,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; @@ -585,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)); @@ -595,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))); } @@ -603,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(); 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; 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; } 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..83fa036804 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 @@ -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 @@ -84,9 +84,8 @@ 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.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 52ded9b659..90457d523e 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 @@ -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()}>" @@ -1041,11 +1063,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 +1097,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/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" 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/CMakeLists.txt b/openage/convert/processor/conversion/aoc/CMakeLists.txt index 00d44e8d3a..5141dfba8e 100644 --- a/openage/convert/processor/conversion/aoc/CMakeLists.txt +++ b/openage/convert/processor/conversion/aoc/CMakeLists.txt @@ -16,3 +16,21 @@ add_py_modules( upgrade_effect_subprocessor.py upgrade_resource_subprocessor.py ) + +add_subdirectory(ability) +add_subdirectory(auxiliary) +add_subdirectory(civ) +add_subdirectory(effect) +add_subdirectory(main) +add_subdirectory(media) +add_subdirectory(modifier) +add_subdirectory(modpack) +add_subdirectory(nyan) +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) +add_subdirectory(upgrade_resource) 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..614701233a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/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/aoc/ability/active_transform_to.py b/openage/convert/processor/conversion/aoc/ability/active_transform_to.py new file mode 100644 index 0000000000..b007bb99f6 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/active_transform_to.py @@ -0,0 +1,22 @@ +# 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. + :returns: The forward reference for the ability. + """ + # 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..826c2cd3ed --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/activity.py @@ -0,0 +1,53 @@ +# 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. + :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}.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..9541d5598d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/apply_continuous_effect.py @@ -0,0 +1,275 @@ +# 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. + :returns: The forward reference for the ability. + """ + 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..f61b87cbb9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/apply_discrete_effect.py @@ -0,0 +1,339 @@ +# 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. + :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] + + 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..b9c1382798 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/attribute_change_tracker.py @@ -0,0 +1,132 @@ +# 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. + :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}.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..710d68095d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/collect_storage.py @@ -0,0 +1,63 @@ +# 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. + :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}.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..0915f1b2de --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/collision.py @@ -0,0 +1,72 @@ +# 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. + :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}.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..096c9c5e28 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/constructable.py @@ -0,0 +1,1301 @@ +# 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. + :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}.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..a298102095 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/create.py @@ -0,0 +1,87 @@ +# 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. + :returns: The forward reference for the ability. + """ + 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..a06e126178 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/death.py @@ -0,0 +1,294 @@ +# 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. + :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) + 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..f146f34bbe --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/delete.py @@ -0,0 +1,125 @@ +# 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, + i.e. die on command. + + :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}.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..faf67bc621 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/despawn.py @@ -0,0 +1,165 @@ +# 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. + :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 + + # 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..c84cd92b73 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/drop_resources.py @@ -0,0 +1,123 @@ +# 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. + :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}.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..3d08e6b827 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/drop_site.py @@ -0,0 +1,90 @@ +# 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. + :returns: The forward reference for the ability. + """ + 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..9e6dd6d4b7 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/enter_container.py @@ -0,0 +1,81 @@ +# 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. + :returns: The forward reference for the ability. None if no valid containers were found. + """ + 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..ad8852c2f8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/exchange_resources.py @@ -0,0 +1,80 @@ +# 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. + :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", "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..95d76fb0c2 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/exit_container.py @@ -0,0 +1,68 @@ +# 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. + :returns: The forward reference for the ability. None if no valid containers were found. + """ + 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..8e1c0e3f9a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/formation.py @@ -0,0 +1,96 @@ +# 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. + :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}.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..3c2dbb0969 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/foundation.py @@ -0,0 +1,56 @@ +# 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. + :param terrain_id: Force this terrain ID as foundation + :returns: The forward references for the abilities. + """ + 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..4f1133fa9b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/game_entity_stance.py @@ -0,0 +1,100 @@ +# 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. + :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", "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..acb2b89ce3 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/gather.py @@ -0,0 +1,254 @@ +# 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) -> 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. + :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.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..065d40e6ae --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/harvestable.py @@ -0,0 +1,395 @@ +# 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. + :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.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..b4a9ac7db4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/herd.py @@ -0,0 +1,92 @@ +# 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. + :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}.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..804913f9c5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/herdable.py @@ -0,0 +1,53 @@ +# 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. + :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}.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..3954fe3edd --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/idle.py @@ -0,0 +1,119 @@ +# 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. + :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) + 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..c836c5f7d4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/line_of_sight.py @@ -0,0 +1,72 @@ +# 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. + :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}.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..5505dc881c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/live.py @@ -0,0 +1,120 @@ +# 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. + :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}.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..7da2671d6d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/move.py @@ -0,0 +1,308 @@ +# 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. + :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) + 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. + :returns: The forward reference for the ability. + """ + 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..cfe9d088bb --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/named.py @@ -0,0 +1,106 @@ +# 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. + :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}.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..572a151cb4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/overlay_terrain.py @@ -0,0 +1,54 @@ +# 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. + :returns: The forward references for the abilities. + """ + 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..26a576ef5b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/pathable.py @@ -0,0 +1,60 @@ +# 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. + :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}.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..c255e6b058 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/production_queue.py @@ -0,0 +1,73 @@ +# 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. + :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 = 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..82c305fdf9 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/projectile.py @@ -0,0 +1,125 @@ +# 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. + :param position: Index of the projectile to add (0 or 1 for AoC). + :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{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..ba220c91e7 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/provide_contingent.py @@ -0,0 +1,93 @@ +# 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 + + +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. + """ + 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..3ae74e506d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/rally_point.py @@ -0,0 +1,42 @@ +# 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. + :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}.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..2d24e3a585 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/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 + 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. + :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 == 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..a0d3f4a13f --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/regenerate_resource_spot.py @@ -0,0 +1,22 @@ +# 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. + :returns: The forward reference for the ability. + """ + # 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..99702de361 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/remove_storage.py @@ -0,0 +1,63 @@ +# 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. + :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}.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..c93b322bdd --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/research.py @@ -0,0 +1,89 @@ +# 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. + :returns: The forward reference for the ability. + """ + 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..b1002d13c4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/resistance.py @@ -0,0 +1,65 @@ +# 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. + :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)): + 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..df647be8b8 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/resource_storage.py @@ -0,0 +1,267 @@ +# 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. + :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.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..216381a822 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/restock.py @@ -0,0 +1,153 @@ +# 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. + :returns: The forward reference for the ability. + """ + 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..2be194641c --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/selectable.py @@ -0,0 +1,236 @@ +# 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. + :returns: The forward reference for the abilities. + """ + 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..0eca12fa47 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/send_back_to_task.py @@ -0,0 +1,54 @@ +# 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. + :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.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..a31eade77b --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/shoot_projectile.py @@ -0,0 +1,288 @@ +# 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. + :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) + + 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..d251cd8b5a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/stop.py @@ -0,0 +1,67 @@ +# 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. + :returns: The forward reference for the ability. + """ + 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..8cc9cdd956 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/storage.py @@ -0,0 +1,452 @@ +# 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. + :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}.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..33f44356ed --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/terrain_requirement.py @@ -0,0 +1,66 @@ +# 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. + :returns: The forward references for the abilities. + """ + 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..74fda97861 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/trade.py @@ -0,0 +1,77 @@ +# 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. + :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 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..865a170d3a --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/trade_post.py @@ -0,0 +1,80 @@ +# 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. + :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.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..e8ad078508 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/transfer_storage.py @@ -0,0 +1,92 @@ +# 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. + :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}.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..ae0b2a4cf2 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/turn.py @@ -0,0 +1,92 @@ +# 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. + :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}.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..18dfeee9e5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/use_contingent.py @@ -0,0 +1,89 @@ +# 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. + :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}.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..2650910a07 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/util.py @@ -0,0 +1,280 @@ +# 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 + + +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. + :param animation_id: ID of the animation in the dataset. + :param ability_ref: Reference of the object the animation is nested in. + :param obj_name_prefix: Name prefix for the animation object. + :param filename_prefix: Prefix for the animation PNG and sprite files. + """ + 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. + :param civ_group: ConverterObjectGroup that patches the animation object into the ability. + :param animation_id: ID of the animation in the dataset. + :param location_ref: Reference of the object the resulting object is nested in. + :param obj_name_prefix: Name prefix for the object. + :param filename_prefix: Prefix for the animation PNG and sprite files. + :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() + 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. + :param sound_id: ID of the sound in the dataset. + :param location_ref: Reference of the object the sound is nested in. + :param obj_name_prefix: Name prefix for the sound object. + :param filename_prefix: Prefix for the animation PNG and sprite files. + """ + 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. + :param string_id: ID of the string in the dataset. + :param location_ref: Reference of the object the string is nested in. + :param obj_name_prefix: Name prefix for the string object. + """ + 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..867a68eecd --- /dev/null +++ b/openage/convert/processor/conversion/aoc/ability/visibility.py @@ -0,0 +1,133 @@ +# 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. + :returns: The forward reference for the ability. + """ + 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 120541fe4c..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-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 -# pylint: disable=invalid-name -# -# TODO: -# pylint: disable=unused-argument,line-too-long +# 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,7540 +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 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] - - 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_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") - - if ranged: - # 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") - - # Max range - if command_id == 105: - # Heal - max_range = 4 - - else: - max_range = current_unit["weapon_range_max"].value - - ability_raw_api_object.add_raw_member("max_range", - max_range, - "engine.ability.type.RangedContinuousEffect") - - # Effects - 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 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, - 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 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_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(ability_parent) - 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") - - if ranged: - # 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") - - # 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") - - # 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) - - 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) - - # 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, - "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") - - 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 - - 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 - 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 = {} - - # 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") - - # 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", - 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/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) 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) 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..96f829c47d --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/attack.py @@ -0,0 +1,115 @@ +# 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. + :param location_ref: Reference to API object the effects are added to. + :returns: The forward references for the effects. + """ + 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..41a301df01 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/construct.py @@ -0,0 +1,104 @@ +# 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. + :param location_ref: Reference to API object the effects are added to. + :returns: The forward references for the effects. + """ + 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..2c53c01a63 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/convert.py @@ -0,0 +1,133 @@ +# 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. + :param location_ref: Reference to API object the effects are added to. + :returns: The forward references for the effects. + """ + 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..78fbf0568e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/heal.py @@ -0,0 +1,108 @@ +# 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. + :param location_ref: Reference to API object the effects are added to. + :returns: The forward references for the effects. + """ + 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..29056ca224 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/effect/repair.py @@ -0,0 +1,137 @@ +# 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. + :param location_ref: Reference to API object the effects are added to. + :returns: The forward references for the effects. + """ + 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/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..ffa453af46 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/building_line.py @@ -0,0 +1,136 @@ +# 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 + + # 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. + 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_head = True + + if building.has_member("head_unit_id") and \ + building["head_unit_id"].value > -1: + stack_building_annex = True + + # 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 + + # 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) + + 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..6b5176b9c4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/unit_line.py @@ -0,0 +1,131 @@ +# 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}) + + +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..8729ef8976 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/groups/villager_group.py @@ -0,0 +1,66 @@ +# 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. + """ + 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 in GenieUnitTaskGroup.line_id_assignments: + line_id = GenieUnitTaskGroup.line_id_assignments[task_group_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}) + 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) + + # Create the villager task group + 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: + 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..a529fc1027 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/main/link/gather.py @@ -0,0 +1,45 @@ +# 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: + 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/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/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) 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) 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) 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) 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..07febc40fc --- /dev/null +++ b/openage/convert/processor/conversion/aoc/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/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..dba56ff038 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/effect.py @@ -0,0 +1,580 @@ +# 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 +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" +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..d5894dc4e7 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/exchange.py @@ -0,0 +1,348 @@ +# 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 +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" +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) + + # 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_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) + + # 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}) 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..ff881b88cf --- /dev/null +++ b/openage/convert/processor/conversion/aoc/pregen/resource.py @@ -0,0 +1,261 @@ +# 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" +NAME_VALUE_PARENT = "engine.util.language.translated.type.TranslatedString" +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) + + 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) + + 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) + + 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) + + 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}) + + 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 48eb8b97e7..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-2024 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 +# 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,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,2266 +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" - xor_parent = "engine.util.activity.node.type.XORGate" - xor_event_parent = "engine.util.activity.node.type.XOREventGate" - - # 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" - - # ======================================================================= - # 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(condition_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_parent) - - condition_forward_ref = ForwardRef(pregen_converter_group, - "util.activity.types.Unit.NextCommandMove") - branch_raw_api_object.add_raw_member("next", - [condition_forward_ref], - xor_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) - - 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 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, - "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.Wait") - 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 (for Move or Command) - wait_ref_in_modpack = "util.activity.types.Unit.Wait" - 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, - # TODO: don't go back to move, go to xor gate that - # branches depending on command - 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) 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 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..5dfcf4c4a4 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/attack.py @@ -0,0 +1,97 @@ +# 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. + :param ability_ref: Reference of the ability raw API object the effects are added to. + :returns: The forward references for the effects. + """ + 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..180b651d17 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/construct.py @@ -0,0 +1,107 @@ +# 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. + :param ability_ref: Reference of the ability raw API object the effects are added to. + :returns: The forward references for the effects. + """ + 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..c4c01a21c5 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/convert.py @@ -0,0 +1,90 @@ +# 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. + :param ability_ref: Reference of the ability raw API object the effects are added to. + :returns: The forward references for the effects. + """ + 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..41020cd230 --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/heal.py @@ -0,0 +1,76 @@ +# 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. + :param ability_ref: Reference of the ability raw API object the effects are added to. + :returns: The forward references for the effects. + """ + 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..997508791e --- /dev/null +++ b/openage/convert/processor/conversion/aoc/resistance/repair.py @@ -0,0 +1,95 @@ +# 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. + :param ability_ref: Reference of the ability raw API object the effects are added to. + :returns: The forward references for the effects. + """ + 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 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) 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 f00814db31..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-2023 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 +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ 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,2097 +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 - - diff_min_range = None - diff_max_range = None - if not data_changed and 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 - - 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) - - 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, - "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 - - 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 - - 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) - - 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, - "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 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 - 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_min_range, - diff_max_range, - 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 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_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", - 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_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 f8c185d5a8..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-2023 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 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,2746 +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 = [] - - 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] - - 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(): - if line.is_ranged(): - patch_target_ref = f"{game_entity_name}.Attack" - 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_forward_ref = ForwardRef(line, patch_target_ref) - patch_target_parent = "engine.ability.type.RangedDiscreteEffect" - - 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] - - 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.RangedDiscreteEffect" - - 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.RangedDiscreteEffect" - - 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) 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 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 477c52ad1e..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-2023 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 +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ 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" - 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.type.RangedContinuousEffect", - 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) 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() diff --git a/openage/convert/processor/conversion/de2/CMakeLists.txt b/openage/convert/processor/conversion/de2/CMakeLists.txt index cbed4dc792..9e35a798af 100644 --- a/openage/convert/processor/conversion/de2/CMakeLists.txt +++ b/openage/convert/processor/conversion/de2/CMakeLists.txt @@ -10,3 +10,12 @@ add_py_modules( upgrade_attribute_subprocessor.py upgrade_resource_subprocessor.py ) + +add_subdirectory(ability) +add_subdirectory(civ) +add_subdirectory(main) +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/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) 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) 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..cc8d5bbe16 --- /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", ignore_duplicates=True) + 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/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) 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) 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}) 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) 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) 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) 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) diff --git a/openage/convert/processor/conversion/ror/CMakeLists.txt b/openage/convert/processor/conversion/ror/CMakeLists.txt index 521e9e6b32..71bac0cfe2 100644 --- a/openage/convert/processor/conversion/ror/CMakeLists.txt +++ b/openage/convert/processor/conversion/ror/CMakeLists.txt @@ -13,3 +13,13 @@ add_py_modules( upgrade_attribute_subprocessor.py upgrade_resource_subprocessor.py ) + +add_subdirectory(ability) +add_subdirectory(auxiliary) +add_subdirectory(civ) +add_subdirectory(main) +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/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 0fbb17cb17..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-2023 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 +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ 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,867 +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] - - 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_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") - - if ranged: - # 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") - - # 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") - - # 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 = {} - - # 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") - - # 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", - 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) 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) 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) 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..c41227656c --- /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 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) + 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, (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: + 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/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) 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}) 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) 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) 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 2b00b1ad60..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-2023 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 +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ 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,221 +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 = 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) - 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 - 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_min_range, - diff_max_range, - diff_reload_time, - diff_spawn_delay, - diff_spawn_area_offsets)): - data_changed = True - - 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_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", - 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) 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) 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) diff --git a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt index 7fe5268a7e..50ba2cd580 100644 --- a/openage/convert/processor/conversion/swgbcc/CMakeLists.txt +++ b/openage/convert/processor/conversion/swgbcc/CMakeLists.txt @@ -11,3 +11,13 @@ add_py_modules( upgrade_attribute_subprocessor.py upgrade_resource_subprocessor.py ) + +add_subdirectory(ability) +add_subdirectory(auxiliary) +add_subdirectory(civ) +add_subdirectory(main) +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/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 07ca29004a..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-2024 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 +# Copyright 2020-2025 the openage authors. See copying.md for legal info. """ 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,1854 +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] - - 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_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") - - if ranged: - # 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") - - # 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") - - # 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 - 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 - - 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) 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) 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) 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..770c642cf7 --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/building_line.py @@ -0,0 +1,175 @@ +# 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) + full_data_set.stack_building_groups.update({building_id: building_line}) + + 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..a85c3e93bb --- /dev/null +++ b/openage/convert/processor/conversion/swgbcc/main/groups/villager_group.py @@ -0,0 +1,67 @@ +# 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 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( + 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}) + 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) + + # Create the villager task group + villager = GenieVillagerGroup(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 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/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) 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 fb96a60698..d0512d7cc9 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,16 +36,17 @@ 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 + # 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) - 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) pregen_nyan_objects = full_data_set.pregen_nyan_objects # Create nyan objects from the raw API objects @@ -66,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) 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) 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) 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) 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 8da0ec95ef..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-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 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" - 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.type.RangedContinuousEffect", - 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) 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..de5cf9a61b 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) @@ -539,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) @@ -546,17 +546,17 @@ 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.NextCommandIdle + # engine.util.activity.condition.type.NextCommand parents = [api_objects["engine.util.activity.condition.Condition"]] - nyan_object = NyanObject("NextCommandIdle", parents) - fqon = "engine.util.activity.condition.type.NextCommandIdle" + nyan_object = NyanObject("NextCommand", parents) + fqon = "engine.util.activity.condition.type.NextCommand" nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) - # engine.util.activity.condition.type.NextCommandMove + # engine.util.activity.condition.type.TargetInRange parents = [api_objects["engine.util.activity.condition.Condition"]] - nyan_object = NyanObject("NextCommandMove", parents) - fqon = "engine.util.activity.condition.type.NextCommandMove" + nyan_object = NyanObject("TargetInRange", parents) + fqon = "engine.util.activity.condition.type.TargetInRange" nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) @@ -616,6 +616,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) @@ -630,6 +637,55 @@ 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.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.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) + 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) @@ -728,6 +784,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) @@ -2574,6 +2658,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"] @@ -2713,8 +2805,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,)) @@ -2863,8 +2953,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"]) @@ -3015,22 +3103,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"] @@ -3133,10 +3205,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) @@ -3280,6 +3348,30 @@ 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"] + + 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"] + + 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.event.type.Wait api_object = api_objects["engine.util.activity.event.type.Wait"] @@ -3304,6 +3396,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"] @@ -3324,6 +3427,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"] 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("**") 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"), 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) 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..5b8715fe68 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"), @@ -253,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"), ] @@ -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"), @@ -1030,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 )), @@ -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"), diff --git a/openage/convert/value_object/read/read_members.py b/openage/convert/value_object/read/read_members.py index 6e07e471b2..e2a681bcca 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 @@ -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 @@ -265,14 +267,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 +281,47 @@ 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], + lookup_dict: dict[int, str], raw_type: str, - file_name: str = None + unknown_lookup_prefix: str = None, ): 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: + self.unknown_lookup_prefix = unknown_lookup_prefix + + def entry_hook(self, data: int) -> str: """ perform lookup of raw data -> enum member name - """ - try: + Throws an error if the lookup fails and no unknown lookup prefix is defined. + """ + 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)}" + except TypeError: h = "" + raise KeyError("failed to find %s%s in lookup dict %s!" % (str(data), h, self.type_name)) from None 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}) 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/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(): 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