diff --git a/doc/code/game_simulation/images/activity_graph.svg b/doc/code/game_simulation/images/activity_graph.svg index bf87f779fa..4044797dc2 100644 --- a/doc/code/game_simulation/images/activity_graph.svg +++ b/doc/code/game_simulation/images/activity_graph.svg @@ -1,4 +1,4 @@ -IdleMoveStartMoveable?Can MoveCan't MoveWait for commandWait for Move to FinishEnd \ No newline at end of file +Idlecommand in queue?wait for commandbranch on commandGatherAttackMovemove finishednew commandcommand receivedMove \ No newline at end of file diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index 8c56586637..bd5b6003ae 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -115,7 +115,7 @@ class BaseCurve : public event::EventEntity { * the keyframes of \p other. */ void sync(const BaseCurve &other, - const time::time_t &start = std::numeric_limits::min()); + const time::time_t &start = time::TIME_MIN); /** * Copy keyframes from another curve (with a different element type) to this curve. @@ -134,7 +134,7 @@ class BaseCurve : public event::EventEntity { template void sync(const BaseCurve &other, const std::function &converter, - const time::time_t &start = std::numeric_limits::min()); + const time::time_t &start = time::TIME_MIN); /** * Get the identifier of this curve. @@ -270,7 +270,7 @@ std::string BaseCurve::str() const { template void BaseCurve::check_integrity() const { - time::time_t last_time = std::numeric_limits::min(); + time::time_t last_time = time::TIME_MIN; for (const auto &keyframe : this->container) { if (keyframe.time < last_time) { throw Error{MSG(err) << "curve is broken after t=" << last_time << ": " << this->str()}; diff --git a/libopenage/curve/iterator.h b/libopenage/curve/iterator.h index 2500e083cd..1062ddcc1e 100644 --- a/libopenage/curve/iterator.h +++ b/libopenage/curve/iterator.h @@ -33,8 +33,8 @@ class CurveIterator { explicit CurveIterator(const container_t *c) : base{}, container{c}, - from{-std::numeric_limits::max()}, - to{+std::numeric_limits::max()} {} + from{-time::TIME_MAX}, + to{+time::TIME_MAX} {} protected: /** diff --git a/libopenage/curve/keyframe.h b/libopenage/curve/keyframe.h index d6b90af4b5..82e36ed147 100644 --- a/libopenage/curve/keyframe.h +++ b/libopenage/curve/keyframe.h @@ -33,7 +33,7 @@ class Keyframe { time{time}, value{value} {} - const time::time_t time = std::numeric_limits::min(); + const time::time_t time = time::TIME_MIN; T value = T{}; }; diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index 1d7079498b..13b50bd04f 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -281,7 +281,7 @@ class KeyframeContainer { * the keyframes of \p other. */ iterator sync(const KeyframeContainer &other, - const time::time_t &start = std::numeric_limits::min()); + const time::time_t &start = time::TIME_MIN); /** * Copy keyframes from another container (with a different element type) to this container. @@ -298,7 +298,7 @@ class KeyframeContainer { template iterator sync(const KeyframeContainer &other, const std::function &converter, - const time::time_t &start = std::numeric_limits::min()); + const time::time_t &start = time::TIME_MIN); /** * Debugging method to be used from gdb to understand bugs better. @@ -328,7 +328,7 @@ 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(std::numeric_limits::min(), T())); + this->container.push_back(keyframe_t(time::TIME_MIN, T())); } @@ -336,7 +336,7 @@ 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(std::numeric_limits::min(), defaultval)); + this->container.push_back(keyframe_t(time::TIME_MIN, defaultval)); } diff --git a/libopenage/curve/map.h b/libopenage/curve/map.h index 3a8374a128..9712c89470 100644 --- a/libopenage/curve/map.h +++ b/libopenage/curve/map.h @@ -48,10 +48,10 @@ class UnorderedMap { at(const time::time_t &, const key_t &) const; MapFilterIterator - begin(const time::time_t &e = std::numeric_limits::max()) const; + begin(const time::time_t &e = time::TIME_MAX) const; MapFilterIterator - end(const time::time_t &e = std::numeric_limits::max()) const; + end(const time::time_t &e = time::TIME_MAX) const; MapFilterIterator insert(const time::time_t &birth, const key_t &, const val_t &); @@ -100,7 +100,7 @@ UnorderedMap::at(const time::time_t &time, e, this, time, - std::numeric_limits::max()); + time::TIME_MAX); } else { return {}; @@ -114,7 +114,7 @@ UnorderedMap::begin(const time::time_t &time) const { this->container.begin(), this, time, - std::numeric_limits::max()); + time::TIME_MAX); } template @@ -123,7 +123,7 @@ UnorderedMap::end(const time::time_t &time) const { return MapFilterIterator>( this->container.end(), this, - -std::numeric_limits::max(), + -time::TIME_MAX, time); } @@ -149,7 +149,7 @@ UnorderedMap::insert(const time::time_t &alive, const val_t &value) { return this->insert( alive, - std::numeric_limits::max(), + time::TIME_MAX, key, value); } diff --git a/libopenage/curve/queue.h b/libopenage/curve/queue.h index 3ac8d20d9a..32675a72d3 100644 --- a/libopenage/curve/queue.h +++ b/libopenage/curve/queue.h @@ -53,7 +53,7 @@ class Queue : public event::EventEntity { EventEntity{loop}, _id{id}, _idstr{idstr}, - last_front{this->container.begin()} {} + last_pop{time::TIME_ZERO} {} // prevent accidental copy of queue Queue(const Queue &) = delete; @@ -69,12 +69,13 @@ class Queue : public event::EventEntity { const T &front(const time::time_t &time) const; /** - * Get the first element in the queue at the given time. + * Get the first element in the queue at the given time and remove it from + * the queue. * * @param time The time to get the element at. * @param value Queue element. */ - const T &pop_front(const time::time_t &time); + const T pop_front(const time::time_t &time); /** * Check if the queue is empty at a given time. @@ -92,7 +93,7 @@ class Queue : public event::EventEntity { * @return Iterator to the first element. */ QueueFilterIterator> begin( - const time::time_t &t = -std::numeric_limits::max()) const; + const time::time_t &t = -time::TIME_MAX) const; /** * Get an iterator to the last element in the queue at the given time. @@ -101,7 +102,7 @@ class Queue : public event::EventEntity { * @return Iterator to the last element. */ QueueFilterIterator> end( - const time::time_t &t = std::numeric_limits::max()) const; + const time::time_t &t = time::TIME_MAX) const; /** * Get an iterator to elements that are in the queue between two time frames. @@ -111,8 +112,8 @@ class Queue : public event::EventEntity { * @return Iterator to the first element in the time frame. */ QueueFilterIterator> between( - const time::time_t &begin = std::numeric_limits::max(), - const time::time_t &end = std::numeric_limits::max()) const; + const time::time_t &begin = time::TIME_MAX, + const time::time_t &end = time::TIME_MAX) const; /** * Erase an element from the queue. @@ -183,24 +184,72 @@ class Queue : public event::EventEntity { */ container_t container; - iterator last_front; + /** + * The time of the last access to the queue. + */ + time::time_t last_pop; }; template -const T &Queue::front(const time::time_t &t) const { - return this->begin(t).value(); +const T &Queue::front(const time::time_t &time) const { + if (this->empty(time)) [[unlikely]] { + throw Error{MSG(err) << "Tried accessing front at " + << time << " but queue is empty."}; + } + + // search for the last element before the given time + auto it = this->container.end(); + --it; + while (it->time() > time and it != this->container.begin()) { + --it; + } + + return it->value; } template -bool Queue::empty(const time::time_t &time) const { - return this->last_front == this->begin(time).get_base(); +const T Queue::pop_front(const time::time_t &time) { + if (this->empty(time)) [[unlikely]] { + throw Error{MSG(err) << "Tried accessing front at " + << time << " but queue is empty."}; + } + + // search for the last element before the given time + auto it = this->container.end(); + --it; + while (it->time() > time and it != this->container.begin()) { + --it; + } + + // get the last element inserted before the given time + auto val = std::move(it->value); + + // get the time span between current time and the next element + auto to = (++it)->time(); + --it; + auto from = time; + + // erase the element + // TODO: We should be able to reinsert elements + auto filter_iterator = QueueFilterIterator>(it, this, to, from); + this->erase(filter_iterator); + + this->last_pop = time; + + return val; } template -inline const T &Queue::pop_front(const time::time_t &time) { - this->last_front = this->begin(time).get_base(); - return this->front(time); +bool Queue::empty(const time::time_t &time) const { + if (this->container.empty()) { + return true; + } + + // search for the first element that is after the given time + auto begin = this->begin(time).get_base(); + + return begin == this->container.begin() and begin->time() > time; } template @@ -211,7 +260,7 @@ QueueFilterIterator> Queue::begin(const time::time_t &t) const { it, this, t, - std::numeric_limits::max()); + time::TIME_MAX); } } @@ -225,20 +274,19 @@ QueueFilterIterator> Queue::end(const time::time_t &t) const { container.end(), this, t, - std::numeric_limits::max()); + time::TIME_MAX); } template -QueueFilterIterator> Queue::between( - const time::time_t &begin, - const time::time_t &end) const { +QueueFilterIterator> Queue::between(const time::time_t &begin, + const time::time_t &end) const { auto it = QueueFilterIterator>( container.begin(), this, begin, end); - if (!container.empty() && !it.valid()) { + if (not it.valid()) { ++it; } return it; @@ -253,9 +301,8 @@ void Queue::erase(const CurveIterator> &it) { template -QueueFilterIterator> Queue::insert( - const time::time_t &time, - const T &e) { +QueueFilterIterator> Queue::insert(const time::time_t &time, + const T &e) { const_iterator insertion_point = this->container.end(); for (auto it = this->container.begin(); it != this->container.end(); ++it) { if (time < it->time()) { @@ -272,7 +319,7 @@ QueueFilterIterator> Queue::insert( insertion_point, this, time, - std::numeric_limits::max()); + time::TIME_MAX); if (!ct.valid()) { ++ct; diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index a5efbf2887..9787243db3 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -149,11 +149,25 @@ void test_queue() { auto loop = std::make_shared(); Queue q{loop, 0}; - q.insert(0, 1); + + TESTEQUALS(q.empty(0), true); + TESTEQUALS(q.empty(1), true); + TESTEQUALS(q.empty(100001), true); + q.insert(2, 2); q.insert(4, 3); q.insert(10, 4); q.insert(100001, 5); + q.insert(100001, 6); + + TESTEQUALS(q.empty(0), true); + TESTEQUALS(q.empty(1), true); + TESTEQUALS(q.empty(2), false); + TESTEQUALS(q.empty(100001), false); + TESTEQUALS(q.empty(100002), false); + + q.insert(0, 1); + TESTEQUALS(*q.begin(0), 1); TESTEQUALS(*q.begin(1), 2); TESTEQUALS(*q.begin(2), 2); @@ -164,6 +178,17 @@ void test_queue() { TESTEQUALS(*q.begin(12), 5); TESTEQUALS(*q.begin(100000), 5); + TESTEQUALS(q.front(0), 1); + TESTEQUALS(q.front(1), 1); + TESTEQUALS(q.front(2), 2); + TESTEQUALS(q.front(3), 2); + TESTEQUALS(q.front(4), 3); + TESTEQUALS(q.front(5), 3); + TESTEQUALS(q.front(10), 4); + TESTEQUALS(q.front(12), 4); + TESTEQUALS(q.front(100000), 4); + TESTEQUALS(q.front(100001), 6); + { std::unordered_set reference = {1, 2, 3}; for (auto it = q.between(0, 6); it != q.end(); ++it) { @@ -204,6 +229,19 @@ void test_queue() { } TESTEQUALS(reference.empty(), true); } + + + TESTEQUALS(q.pop_front(0), 1); + TESTEQUALS(q.empty(0), true); + + TESTEQUALS(q.pop_front(12), 4); + TESTEQUALS(q.empty(12), false); + + TESTEQUALS(q.pop_front(12), 3); + TESTEQUALS(q.empty(12), false); + + TESTEQUALS(q.pop_front(12), 2); + TESTEQUALS(q.empty(12), true); } diff --git a/libopenage/curve/tests/curve_types.cpp b/libopenage/curve/tests/curve_types.cpp index cab8473bc3..40f20395ee 100644 --- a/libopenage/curve/tests/curve_types.cpp +++ b/libopenage/curve/tests/curve_types.cpp @@ -37,7 +37,7 @@ void curve_types() { { auto it = c.begin(); TESTEQUALS(it->value, 0); - TESTEQUALS(it->time, std::numeric_limits::min()); + TESTEQUALS(it->time, time::TIME_MIN); TESTEQUALS((++it)->time, 0); TESTEQUALS(it->value, 0); TESTEQUALS((++it)->time, 1); @@ -140,7 +140,7 @@ void curve_types() { { auto it = c.begin(); - TESTEQUALS(it->time, std::numeric_limits::min()); + TESTEQUALS(it->time, time::TIME_MIN); TESTEQUALS(it->value, 0); TESTEQUALS((++it)->time, 0); diff --git a/libopenage/event/demo/physics.cpp b/libopenage/event/demo/physics.cpp index d43365f042..e27a51d962 100644 --- a/libopenage/event/demo/physics.cpp +++ b/libopenage/event/demo/physics.cpp @@ -104,7 +104,7 @@ class BallReflectWall : public DependencyEventHandler { auto pos = positioncurve->get(now); if (speed[1] == 0) { - return std::numeric_limits::max(); + return time::TIME_MAX; } time::time_t ty = 0; @@ -227,7 +227,7 @@ class BallReflectPanel : public DependencyEventHandler { auto pos = positioncurve->get(now); if (speed[0] == 0) - return std::numeric_limits::max(); + return time::TIME_MAX; time::time_t ty = 0; diff --git a/libopenage/event/event.h b/libopenage/event/event.h index 72c943d807..34fcbc0cee 100644 --- a/libopenage/event/event.h +++ b/libopenage/event/event.h @@ -12,6 +12,8 @@ namespace openage::event { class EventEntity; +using event_hash_t = size_t; + /** * The actual one event that may be called - it is used to manage the event itself. * It does not need to be stored. @@ -36,7 +38,7 @@ class Event : public std::enable_shared_from_this { */ void reschedule(const time::time_t reference_time); - size_t hash() const { + event_hash_t hash() const { return this->myhash; } @@ -111,7 +113,7 @@ class Event : public std::enable_shared_from_this { time::time_t last_change_time = time::time_t::min_value(); /** Precalculated std::hash for the event */ - size_t myhash; + event_hash_t myhash; }; diff --git a/libopenage/event/event_loop.cpp b/libopenage/event/event_loop.cpp index eb9e8ca6f1..26bf176978 100644 --- a/libopenage/event/event_loop.cpp +++ b/libopenage/event/event_loop.cpp @@ -151,7 +151,7 @@ int EventLoop::execute_events(const time::time_t &time_until, time::time_t new_time = event->get_eventhandler()->predict_invoke_time( target, state, event->get_time()); - if (new_time != std::numeric_limits::min()) { + if (new_time != time::TIME_MIN) { event->set_time(new_time); log::log(DBG << "Loop: repeating event \"" << event->get_eventhandler()->id() @@ -204,7 +204,7 @@ void EventLoop::update_changes(const std::shared_ptr &state) { time::time_t new_time = evnt->get_eventhandler() ->predict_invoke_time(entity, state, change.time); - if (new_time != std::numeric_limits::min()) { + if (new_time != time::TIME_MIN) { log::log(DBG << "Loop: due to a change, rescheduling event of '" << evnt->get_eventhandler()->id() << "' on entity '" << entity->idstr() diff --git a/libopenage/event/eventqueue.cpp b/libopenage/event/eventqueue.cpp index 83a96e6e16..51a0c2fdaa 100644 --- a/libopenage/event/eventqueue.cpp +++ b/libopenage/event/eventqueue.cpp @@ -35,7 +35,7 @@ std::shared_ptr EventQueue::create_event(const std::shared_ptrset_time(event->get_eventhandler() ->predict_invoke_time(trgt, state, reference_time)); - if (event->get_time() == std::numeric_limits::min()) { + if (event->get_time() == time::TIME_MIN) { log::log(DBG << "Queue: ignoring insertion of event " << event->get_eventhandler()->id() << " because no execution was scheduled."); diff --git a/libopenage/gamestate/activity/CMakeLists.txt b/libopenage/gamestate/activity/CMakeLists.txt index 27b43c1776..78a78e7ab0 100644 --- a/libopenage/gamestate/activity/CMakeLists.txt +++ b/libopenage/gamestate/activity/CMakeLists.txt @@ -1,12 +1,15 @@ add_sources(libopenage activity.cpp end_node.cpp - event_node.cpp node.cpp start_node.cpp task_node.cpp task_system_node.cpp tests.cpp types.cpp - xor_node.cpp + xor_event_gate.cpp + xor_gate.cpp ) + +add_subdirectory("event") +add_subdirectory("condition") diff --git a/libopenage/gamestate/activity/activity.cpp b/libopenage/gamestate/activity/activity.cpp index 7ee478eda8..9af770b8fd 100644 --- a/libopenage/gamestate/activity/activity.cpp +++ b/libopenage/gamestate/activity/activity.cpp @@ -6,8 +6,8 @@ namespace openage::gamestate::activity { Activity::Activity(activity_id id, - activity_label label, - const std::shared_ptr &start) : + const std::shared_ptr &start, + activity_label label) : id{id}, label{label}, start{start} { diff --git a/libopenage/gamestate/activity/activity.h b/libopenage/gamestate/activity/activity.h index f5be9d254a..d0832068ad 100644 --- a/libopenage/gamestate/activity/activity.h +++ b/libopenage/gamestate/activity/activity.h @@ -18,19 +18,52 @@ using activity_label = std::string; */ class Activity { public: + /** + * Create a new activity. + * + * @param id Unique ID. + * @param start Start node in the graph. + * @param label Human-readable label (optional). + */ Activity(activity_id id, - activity_label label = "", - const std::shared_ptr &start = {}); + const std::shared_ptr &start, + activity_label label = ""); + /** + * Get the unique ID of this activity. + * + * @return Unique ID. + */ activity_id get_id() const; + /** + * Get the human-readable label of this activity. + * + * @return Human-readable label. + */ const activity_label get_label() const; + /** + * Get the start node of this activity. + * + * @return Start node. + */ const std::shared_ptr &get_start() const; private: + /** + * Unique ID. + */ const activity_id id; + + /** + * Human-readable label. + */ const activity_label label; + + /** + * Start node. + */ std::shared_ptr start; }; diff --git a/libopenage/gamestate/activity/condition/CMakeLists.txt b/libopenage/gamestate/activity/condition/CMakeLists.txt new file mode 100644 index 0000000000..aabd159b0c --- /dev/null +++ b/libopenage/gamestate/activity/condition/CMakeLists.txt @@ -0,0 +1,4 @@ +add_sources(libopenage + command_in_queue.cpp + next_command.cpp +) diff --git a/libopenage/gamestate/activity/condition/command_in_queue.cpp b/libopenage/gamestate/activity/condition/command_in_queue.cpp new file mode 100644 index 0000000000..7e300701f1 --- /dev/null +++ b/libopenage/gamestate/activity/condition/command_in_queue.cpp @@ -0,0 +1,19 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "next_command.h" + +#include "gamestate/component/internal/command_queue.h" +#include "gamestate/game_entity.h" + + +namespace openage::gamestate::activity { + +bool command_in_queue(const time::time_t &time, + const std::shared_ptr &entity) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + + return not command_queue->get_queue().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 new file mode 100644 index 0000000000..67c7a794cc --- /dev/null +++ b/libopenage/gamestate/activity/condition/command_in_queue.h @@ -0,0 +1,28 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace openage::gamestate { +class GameEntity; + +namespace activity { + +/** + * Condition for command in queue check in the activity system. + * + * @param time Time when the condition is checked. + * @param entity Game entity. + * + * @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); + +} // namespace activity +} // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/condition/next_command.cpp b/libopenage/gamestate/activity/condition/next_command.cpp new file mode 100644 index 0000000000..c0b619a783 --- /dev/null +++ b/libopenage/gamestate/activity/condition/next_command.cpp @@ -0,0 +1,37 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "next_command.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) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + + if (command_queue->get_queue().empty(time)) { + return false; + } + + auto command = command_queue->get_queue().front(time); + return command->get_type() == component::command::command_t::MOVE; +} + +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 command = command_queue->get_queue().front(time); + return command->get_type() == component::command::command_t::MOVE; +} + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/condition/next_command.h b/libopenage/gamestate/activity/condition/next_command.h new file mode 100644 index 0000000000..046a18cec6 --- /dev/null +++ b/libopenage/gamestate/activity/condition/next_command.h @@ -0,0 +1,39 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace openage::gamestate { +class GameEntity; + +namespace activity { + +/** + * Condition for next command check in the activity system. + * + * @param time Time when the condition is checked. + * @param entity Game entity. + * + * @return true if the entity has 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. + * + * @param time Time when the condition is checked. + * @param entity Game entity. + * + * @return true if the entity has a move command next in the queue, false otherwise. + */ +bool next_command_move(const time::time_t &time, + const std::shared_ptr &entity); + +} // namespace activity +} // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/end_node.cpp b/libopenage/gamestate/activity/end_node.cpp index 01ecfebade..f933a08172 100644 --- a/libopenage/gamestate/activity/end_node.cpp +++ b/libopenage/gamestate/activity/end_node.cpp @@ -8,13 +8,9 @@ namespace openage::gamestate::activity { -EndNode::EndNode(node_id id, - node_label label) : +EndNode::EndNode(node_id_t id, + node_label_t label) : Node{id, label, {}} { } -void EndNode::add_output(const std::shared_ptr & /* output */) { - throw Error{ERR << "End node cannot have outputs"}; -} - } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/end_node.h b/libopenage/gamestate/activity/end_node.h index c068f39475..e20323d04a 100644 --- a/libopenage/gamestate/activity/end_node.h +++ b/libopenage/gamestate/activity/end_node.h @@ -26,22 +26,13 @@ class EndNode : public Node { * @param id Unique identifier for this node. * @param label Human-readable label (optional). */ - EndNode(node_id id, - node_label label = "End"); + EndNode(node_id_t id, + node_label_t label = "End"); virtual ~EndNode() = default; inline node_t get_type() const override { return node_t::END; } - - /** - * Throws an error since end nodes are not supposed to have outputs - * - * @param output Output node. - * - * @throws openage::Error - */ - [[noreturn]] void add_output(const std::shared_ptr &output) override; }; } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/event/CMakeLists.txt b/libopenage/gamestate/activity/event/CMakeLists.txt new file mode 100644 index 0000000000..863fa6d28a --- /dev/null +++ b/libopenage/gamestate/activity/event/CMakeLists.txt @@ -0,0 +1,4 @@ +add_sources(libopenage + command_in_queue.cpp + wait.cpp +) diff --git a/libopenage/gamestate/activity/event/command_in_queue.cpp b/libopenage/gamestate/activity/event/command_in_queue.cpp new file mode 100644 index 0000000000..af57692078 --- /dev/null +++ b/libopenage/gamestate/activity/event/command_in_queue.cpp @@ -0,0 +1,35 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "command_in_queue.h" + +#include "event/event_loop.h" +#include "event/evententity.h" +#include "gamestate/component/internal/command_queue.h" +#include "gamestate/game_entity.h" +#include "gamestate/game_state.h" +#include "gamestate/manager.h" + + +namespace openage::gamestate::activity { + +std::shared_ptr primer_command_in_queue(const time::time_t &, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, + size_t next_id) { + openage::event::EventHandler::param_map::map_t params{{"next", next_id}}; // move->get_id(); + auto ev = loop->create_event("game.process_command", + entity->get_manager(), + state, + // event is not executed until a command is available + time::TIME_MAX, + params); + auto entity_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + auto &queue = entity_queue->get_queue(); + queue.add_dependent(ev); + + return ev; +}; + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/event/command_in_queue.h b/libopenage/gamestate/activity/event/command_in_queue.h new file mode 100644 index 0000000000..ebb71c166d --- /dev/null +++ b/libopenage/gamestate/activity/event/command_in_queue.h @@ -0,0 +1,42 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace openage { +namespace event { +class Event; +class EventLoop; +} // namespace event + +namespace gamestate { +class GameEntity; +class GameState; + +namespace activity { + +/** + * Primer for command in queue events in the activity system. + * + * @param time Current simulation time. + * @param entity Game entity. + * @param loop Event loop that the event is registered on. + * @param state Game state. + * @param next_id ID of the next node in the activity graph. + * + * @return Scheduled event. + */ +std::shared_ptr primer_command_in_queue(const time::time_t &, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, + size_t next_id); + +} // namespace activity +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/activity/event/wait.cpp b/libopenage/gamestate/activity/event/wait.cpp new file mode 100644 index 0000000000..6bfd03f632 --- /dev/null +++ b/libopenage/gamestate/activity/event/wait.cpp @@ -0,0 +1,29 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "wait.h" + +#include "event/event_loop.h" +#include "event/evententity.h" +#include "gamestate/game_entity.h" +#include "gamestate/game_state.h" +#include "gamestate/manager.h" + + +namespace openage::gamestate::activity { + +std::shared_ptr primer_wait(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, + size_t next_id) { + openage::event::EventHandler::param_map::map_t params{{"next", next_id}}; + auto ev = loop->create_event("game.wait", + entity->get_manager(), + state, + time, + params); + + return ev; +}; + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/event/wait.h b/libopenage/gamestate/activity/event/wait.h new file mode 100644 index 0000000000..c33be2a5be --- /dev/null +++ b/libopenage/gamestate/activity/event/wait.h @@ -0,0 +1,44 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace openage { +namespace event { +class Event; +class EventLoop; +} // namespace event + +namespace gamestate { +class GameEntity; +class GameState; + +namespace activity { + + +/** + * Primer for wait events in the activity system. + * + * @param time Wait until this time. If the time is in the past, the event is executed immediately. + * @param entity Game entity. + * @param loop Event loop that the event is registered on. + * @param state Game state. + * @param next_id ID of the next node in the activity graph. + * + * @return Scheduled event. + */ +std::shared_ptr primer_wait(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, + size_t next_id); + + +} // namespace activity +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/activity/event_node.cpp b/libopenage/gamestate/activity/event_node.cpp deleted file mode 100644 index 3d28e007a3..0000000000 --- a/libopenage/gamestate/activity/event_node.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. - -#include "event_node.h" - -#include - - -namespace openage::gamestate::activity { - -XorEventGate::XorEventGate(node_id id, - node_label label, - const std::vector> &outputs, - event_primer_func_t primer_func, - event_next_func_t next_func) : - Node{id, label, outputs}, - primer_func{primer_func}, - next_func{next_func} { -} - -void XorEventGate::add_output(const std::shared_ptr &output) { - this->outputs.emplace(output->get_id(), output); -} - -void XorEventGate::set_primer_func(event_primer_func_t primer_func) { - this->primer_func = primer_func; -} - -void XorEventGate::set_next_func(event_next_func_t next_func) { - this->next_func = next_func; -} - -event_primer_func_t XorEventGate::get_primer_func() const { - return this->primer_func; -} - -event_next_func_t XorEventGate::get_next_func() const { - return this->next_func; -} - -} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/event_node.h b/libopenage/gamestate/activity/event_node.h deleted file mode 100644 index a753682b68..0000000000 --- a/libopenage/gamestate/activity/event_node.h +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "error/error.h" -#include "log/message.h" - -#include "gamestate/activity/node.h" -#include "gamestate/activity/types.h" -#include "time/time.h" - - -namespace openage { -namespace event { -class Event; -class EventLoop; -} // namespace event - -namespace gamestate { -class GameEntity; -class GameState; - -namespace activity { - -using event_store_t = std::vector>; - -/* */ -/** - * Create and register an event on the event loop - * - * @param time Time at which the primer function is executed. - * @param entity Game entity that the node is associated with. - * @param loop Event loop that events are registered on. - * @param state Game state. - * - * @return List of events registered on the event loop. - */ -using event_primer_func_t = std::function &, - const std::shared_ptr &, - const std::shared_ptr &)>; - -/** - * Decide which node to visit after the event is handled. - * - * @param time Time at which the next function is executed. - * @param entity Game entity that the node is associated with. - * @param loop Event loop that events are registered on. - * @param state Game state. - * - * @return ID of the next node to visit. - */ -using event_next_func_t = std::function &, - const std::shared_ptr &, - const std::shared_ptr &)>; - - -static const event_primer_func_t no_event = [](const time::time_t &, - const std::shared_ptr &, - const std::shared_ptr &, - const std::shared_ptr &) { - throw Error{ERR << "No event primer function registered."}; - return event_store_t{}; -}; - -static const event_next_func_t no_next = [](const time::time_t &, - const std::shared_ptr &, - const std::shared_ptr &, - const std::shared_ptr &) { - throw Error{ERR << "No event next function registered."}; - return 0; -}; - - -/** - * Waits for an event to be executed before continuing the control flow. - */ -class XorEventGate : public Node { -public: - /** - * Create a new exclusive event gateway. - * - * @param id Unique identifier for this node. - * @param label Human-readable label (optional). - * @param outputs Output nodes (can be set later). - * @param primer_func Function to create and register the event. - * @param next_func Function to decide which node to visit after the event is handled. - */ - XorEventGate(node_id id, - node_label label = "Event", - const std::vector> &outputs = {}, - event_primer_func_t primer_func = no_event, - event_next_func_t next_func = no_next); - virtual ~XorEventGate() = default; - - inline node_t get_type() const override { - return node_t::XOR_EVENT_GATE; - } - - /** - * Add an output node. - * - * @param output Output node. - */ - void add_output(const std::shared_ptr &output) override; - - /** - * Set the function to create the event. - * - * @param primer_func Event creation function. - */ - void set_primer_func(event_primer_func_t primer_func); - - /** - * Set the function to decide which node to visit after the event is handled. - * - * @param next_func Next node function. - */ - void set_next_func(event_next_func_t next_func); - - /** - * Get the function to create the event. - * - * @return Event creation function. - */ - event_primer_func_t get_primer_func() const; - - /** - * Get the function to decide which node to visit after the event is handled. - * - * @return Next node function. - */ - event_next_func_t get_next_func() const; - -private: - /** - * Creates the event when the node is visited. - */ - event_primer_func_t primer_func; - - /** - * Decide which node to visit after the event is handled. - */ - event_next_func_t next_func; -}; - -} // namespace activity -} // namespace gamestate -} // namespace openage diff --git a/libopenage/gamestate/activity/node.cpp b/libopenage/gamestate/activity/node.cpp index 5c7f007d74..833b4031eb 100644 --- a/libopenage/gamestate/activity/node.cpp +++ b/libopenage/gamestate/activity/node.cpp @@ -10,8 +10,8 @@ namespace openage::gamestate::activity { -Node::Node(node_id id, - node_label label, +Node::Node(node_id_t id, + node_label_t label, const std::vector> &outputs) : outputs{}, id{id}, @@ -22,11 +22,11 @@ Node::Node(node_id id, } } -node_id Node::get_id() const { +node_id_t Node::get_id() const { return this->id; } -const node_label Node::get_label() const { +const node_label_t Node::get_label() const { return this->label; } @@ -42,7 +42,7 @@ std::string Node::str() const { return ret.str(); } -const std::shared_ptr &Node::next(node_id id) const { +const std::shared_ptr &Node::next(node_id_t id) const { if (not this->outputs.contains(id)) [[unlikely]] { throw Error{MSG(err) << "Node " << this->str() << " has no output with id " << id}; } diff --git a/libopenage/gamestate/activity/node.h b/libopenage/gamestate/activity/node.h index 9a8edfcf9f..ed56277930 100644 --- a/libopenage/gamestate/activity/node.h +++ b/libopenage/gamestate/activity/node.h @@ -13,8 +13,8 @@ namespace openage::gamestate::activity { -using node_id = size_t; -using node_label = std::string; +using node_id_t = size_t; +using node_label_t = std::string; /** * Node in the flow graph describing the activity. @@ -28,8 +28,8 @@ class Node { * @param label Human-readable label (optional). * @param outputs Output nodes. */ - Node(node_id id, - node_label label = "", + Node(node_id_t id, + node_label_t label = "", const std::vector> &outputs = {}); virtual ~Node() = default; @@ -45,14 +45,14 @@ class Node { * * @return The unique identifier. */ - node_id get_id() const; + node_id_t get_id() const; /** * Get the human-readable label for this node. * * @return Human-readable label. */ - const node_label get_label() const; + const node_label_t get_label() const; /** * Get a human-readable string representation of this node. @@ -67,31 +67,24 @@ class Node { * @param id Unique identifier of the output node. * @return Output node. */ - const std::shared_ptr &next(node_id id) const; - - /** - * Add an output node. - * - * @param output Output node. - */ - virtual void add_output(const std::shared_ptr &output) = 0; + const std::shared_ptr &next(node_id_t id) const; protected: /** * Output nodes. */ - std::unordered_map> outputs; + std::unordered_map> outputs; private: /** * Unique identifier for this node. */ - const node_id id; + const node_id_t id; /** * Human-readable label. */ - const node_label label; + const node_label_t label; }; } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/start_node.cpp b/libopenage/gamestate/activity/start_node.cpp index 8686fc2f4e..a6b400fb1e 100644 --- a/libopenage/gamestate/activity/start_node.cpp +++ b/libopenage/gamestate/activity/start_node.cpp @@ -8,8 +8,8 @@ namespace openage::gamestate::activity { -StartNode::StartNode(node_id id, - node_label label, +StartNode::StartNode(node_id_t id, + node_label_t label, const std::shared_ptr &output) : Node{id, label} { if (output) { @@ -22,7 +22,7 @@ void StartNode::add_output(const std::shared_ptr &output) { this->outputs.emplace(output->get_id(), output); } -node_id StartNode::get_next() const { +node_id_t StartNode::get_next() const { return (*this->outputs.begin()).first; } diff --git a/libopenage/gamestate/activity/start_node.h b/libopenage/gamestate/activity/start_node.h index bec65e6ea7..6a9406b381 100644 --- a/libopenage/gamestate/activity/start_node.h +++ b/libopenage/gamestate/activity/start_node.h @@ -25,8 +25,8 @@ class StartNode : public Node { * @param label Human-readable label (optional). * @param output Next node to visit (can be set later). */ - StartNode(node_id id, - node_label label = "Start", + StartNode(node_id_t id, + node_label_t label = "Start", const std::shared_ptr &output = nullptr); virtual ~StartNode() = default; @@ -41,7 +41,7 @@ class StartNode : public Node { * * @param output Output node. */ - void add_output(const std::shared_ptr &output) override; + void add_output(const std::shared_ptr &output); /** * Get the next node to visit. @@ -49,7 +49,7 @@ class StartNode : public Node { * @param time Current time. * @return Next node to visit. */ - node_id get_next() const; + node_id_t get_next() const; }; } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/task_node.cpp b/libopenage/gamestate/activity/task_node.cpp index 36b5de6663..59186eb451 100644 --- a/libopenage/gamestate/activity/task_node.cpp +++ b/libopenage/gamestate/activity/task_node.cpp @@ -8,8 +8,8 @@ namespace openage::gamestate::activity { -TaskCustom::TaskCustom(node_id id, - node_label label, +TaskCustom::TaskCustom(node_id_t id, + node_label_t label, const std::shared_ptr &output, task_func_t task_func) : Node{id, label}, @@ -32,7 +32,7 @@ task_func_t TaskCustom::get_task_func() const { return this->task_func; } -node_id TaskCustom::get_next() const { +node_id_t TaskCustom::get_next() const { return (*this->outputs.begin()).first; } diff --git a/libopenage/gamestate/activity/task_node.h b/libopenage/gamestate/activity/task_node.h index 0efd3f784b..a5eddf59c2 100644 --- a/libopenage/gamestate/activity/task_node.h +++ b/libopenage/gamestate/activity/task_node.h @@ -39,8 +39,8 @@ class TaskCustom : public Node { * @param task_func Action to perform when visiting this node (can be set later). * @param output Next node to visit (optional). */ - TaskCustom(node_id id, - node_label label = "TaskCustom", + TaskCustom(node_id_t id, + node_label_t label = "TaskCustom", const std::shared_ptr &output = nullptr, task_func_t task_func = no_task); virtual ~TaskCustom() = default; @@ -54,7 +54,7 @@ class TaskCustom : public Node { * * @param output Output node. */ - void add_output(const std::shared_ptr &output) override; + void add_output(const std::shared_ptr &output); /** * Set the task function. @@ -76,7 +76,7 @@ class TaskCustom : public Node { * @param time Current time. * @return Next node to visit. */ - node_id get_next() const; + node_id_t get_next() const; private: /** diff --git a/libopenage/gamestate/activity/task_system_node.cpp b/libopenage/gamestate/activity/task_system_node.cpp index 158bcc41b2..c73e00475e 100644 --- a/libopenage/gamestate/activity/task_system_node.cpp +++ b/libopenage/gamestate/activity/task_system_node.cpp @@ -8,8 +8,8 @@ namespace openage::gamestate::activity { -TaskSystemNode::TaskSystemNode(node_id id, - node_label label, +TaskSystemNode::TaskSystemNode(node_id_t id, + node_label_t label, const std::shared_ptr &output, system::system_id_t system_id) : Node{id, label}, @@ -32,7 +32,7 @@ system::system_id_t TaskSystemNode::get_system_id() const { return this->system_id; } -node_id TaskSystemNode::get_next() const { +node_id_t TaskSystemNode::get_next() const { return (*this->outputs.begin()).first; } diff --git a/libopenage/gamestate/activity/task_system_node.h b/libopenage/gamestate/activity/task_system_node.h index 8f9e6b1163..74513cbd64 100644 --- a/libopenage/gamestate/activity/task_system_node.h +++ b/libopenage/gamestate/activity/task_system_node.h @@ -25,8 +25,8 @@ class TaskSystemNode : public Node { * @param output Next node to visit (optional). * @param system_id System to run when visiting this node (can be set later). */ - TaskSystemNode(node_id id, - node_label label = "TaskSystem", + TaskSystemNode(node_id_t id, + node_label_t label = "TaskSystem", const std::shared_ptr &output = nullptr, system::system_id_t system_id = system::system_id_t::NONE); virtual ~TaskSystemNode() = default; @@ -40,7 +40,7 @@ class TaskSystemNode : public Node { * * @param output Output node. */ - void add_output(const std::shared_ptr &output) override; + void add_output(const std::shared_ptr &output); /** * Set the system id. @@ -62,7 +62,7 @@ class TaskSystemNode : public Node { * @param time Current time. * @return Next node to visit. */ - node_id get_next() const; + node_id_t get_next() const; private: /** diff --git a/libopenage/gamestate/activity/tests.cpp b/libopenage/gamestate/activity/tests.cpp index 7588e5a9b0..1ddad0e7e8 100644 --- a/libopenage/gamestate/activity/tests.cpp +++ b/libopenage/gamestate/activity/tests.cpp @@ -15,12 +15,12 @@ #include "log/message.h" #include "gamestate/activity/end_node.h" -#include "gamestate/activity/event_node.h" #include "gamestate/activity/node.h" #include "gamestate/activity/start_node.h" #include "gamestate/activity/task_node.h" #include "gamestate/activity/types.h" -#include "gamestate/activity/xor_node.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" #include "time/time.h" @@ -33,7 +33,8 @@ namespace openage::gamestate::tests { * @param current_node Node where the control flow starts from. * @return Node where the control flow should continue. */ -const std::shared_ptr activity_flow(const std::shared_ptr ¤t_node); +const std::shared_ptr activity_flow(const std::shared_ptr ¤t_node, + const std::optional ev_params = std::nullopt); /** @@ -57,11 +58,11 @@ class TestActivityManager : public event::EventEntity { return "TestActivityManager"; } - void run() { + void run(const std::optional ev_params = std::nullopt) { if (not current_node) { throw Error{ERR << "No current node given"}; } - this->current_node = activity_flow(this->current_node); + this->current_node = activity_flow(this->current_node, ev_params); } std::shared_ptr current_node; @@ -93,9 +94,9 @@ class TestActivityHandler : public event::OnceEventHandler { const std::shared_ptr &target, const std::shared_ptr & /* state */, const time::time_t & /* time */, - const param_map & /* params */) override { + const param_map ¶ms) override { auto mgr_target = std::dynamic_pointer_cast(target); - mgr_target->run(); + mgr_target->run(params); } time::time_t predict_invoke_time(const std::shared_ptr & /* target */, @@ -106,14 +107,27 @@ class TestActivityHandler : public event::OnceEventHandler { }; -const std::shared_ptr activity_flow(const std::shared_ptr ¤t_node) { +const std::shared_ptr activity_flow(const std::shared_ptr ¤t_node, + const std::optional ev_params) { + // events that are currently being listened for + // in the gamestate these are stored in the activity component + static std::vector> events; + auto current = current_node; if (current->get_type() == activity::node_t::XOR_EVENT_GATE) { - auto node = std::static_pointer_cast(current); - auto event_next = node->get_next_func(); - auto next_id = event_next(0, nullptr, nullptr, nullptr); - current = node->next(next_id); + log::log(INFO << "Continuing from event node"); + if (not ev_params.has_value()) { + throw Error{ERR << "XorEventGate: No event parameters given on continue"}; + } + + auto next_id = ev_params.value().get("next"); + current = current->next(next_id); + + // cancel all other events that the manager may have been waiting for + for (auto &event : events) { + event->cancel(0); + } } while (current->get_type() != activity::node_t::END) { @@ -138,16 +152,30 @@ const std::shared_ptr activity_flow(const std::shared_ptr(current); - auto condition = node->get_condition_func(); - auto next_id = condition(0, nullptr); + auto next_id = node->get_default()->get_id(); + for (auto &condition : node->get_conditions()) { + auto condition_func = condition.second; + if (condition_func(0, nullptr)) { + next_id = condition.first; + break; + } + } current = node->next(next_id); } break; case activity::node_t::XOR_EVENT_GATE: { auto node = std::static_pointer_cast(current); - auto event_primer = node->get_primer_func(); - event_primer(0, nullptr, nullptr, nullptr); + auto event_primers = node->get_primers(); + for (auto &primer : event_primers) { + auto ev = primer.second(0, + nullptr, + nullptr, + nullptr, + primer.first); + events.push_back(ev); + } // wait for event + log::log(INFO << "Waiting for event"); return current; } break; default: @@ -157,7 +185,7 @@ const std::shared_ptr activity_flow(const std::shared_ptrstr()); } - log::log(INFO << "Reached end note: " << current->str()); + log::log(INFO << "Reached end node: " << current->str()); return current; } @@ -203,44 +231,44 @@ void activity_demo() { }); // Conditional branch - size_t counter = 0; - xor_node->add_output(task1); - xor_node->add_output(event_node); - xor_node->set_condition_func([&](const time::time_t & /* time */, - const std::shared_ptr & /* entity */) { + static size_t counter = 0; + activity::condition_t branch_task1 = [&](const time::time_t & /* time */, + const std::shared_ptr & /* entity */) { 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() << ")"); counter++; - return task1->get_id(); + return true; } - + return false; + }; + xor_node->add_output(task1, branch_task1); + activity::condition_t branch_event = [&](const time::time_t & /* time */, + const std::shared_ptr & /* entity */) { + // No check needed here, the event node is always selected log::log(INFO << "Selecting path 2 (to event node " << event_node->get_id() << ")"); - - return event_node->get_id(); - }); + return true; + }; + xor_node->add_output(event_node, branch_event); + xor_node->set_default(event_node); // event node - event_node->add_output(task2); - event_node->set_primer_func([&](const time::time_t & /* time */, - const std::shared_ptr & /* entity */, - const std::shared_ptr & /* loop */, - const std::shared_ptr & /* state */) { + activity::event_primer_t primer = [&](const time::time_t & /* time */, + const std::shared_ptr & /* entity */, + const std::shared_ptr & /* loop */, + const std::shared_ptr & /* state */, + size_t next_id) { log::log(INFO << "Setting up event"); + event::EventHandler::param_map::map_t params{{"next", next_id}}; auto ev = loop->create_event("test.activity", mgr, state, - 0); - return activity::event_store_t{ev}; - }); - event_node->set_next_func([&task2](const time::time_t & /* time */, - const std::shared_ptr & /* entity */, - const std::shared_ptr & /* loop */, - const std::shared_ptr & /* state */) { - log::log(INFO << "Selecting next node (task node " << task2->get_id() << ")"); - return task2->get_id(); - }); + 0, + params); + return ev; + }; + event_node->add_output(task2, primer); // task 2 task2->add_output(end); diff --git a/libopenage/gamestate/activity/xor_event_gate.cpp b/libopenage/gamestate/activity/xor_event_gate.cpp new file mode 100644 index 0000000000..f018cfd96a --- /dev/null +++ b/libopenage/gamestate/activity/xor_event_gate.cpp @@ -0,0 +1,42 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "xor_event_gate.h" + +#include + + +namespace openage::gamestate::activity { + +XorEventGate::XorEventGate(node_id_t id, + node_label_t label) : + Node{id, label}, + primers{} { +} + +XorEventGate::XorEventGate(node_id_t id, + node_label_t label, + const std::vector> &outputs, + const std::map &primers) : + Node{id, label, outputs}, + primers{} { + if (primers.size() != outputs.size()) { + throw Error{MSG(err) << "XorEventGate " << this->str() << " has " << outputs.size() + << " outputs but " << primers.size() << " primers"}; + } + + for (const auto &[id, primer] : primers) { + this->primers.emplace(id, primer); + } +} + +void XorEventGate::add_output(const std::shared_ptr &output, + const event_primer_t &primer) { + this->outputs.emplace(output->get_id(), output); + this->primers.emplace(output->get_id(), primer); +} + +const std::map &XorEventGate::get_primers() const { + return this->primers; +} + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/xor_event_gate.h b/libopenage/gamestate/activity/xor_event_gate.h new file mode 100644 index 0000000000..6c4912d714 --- /dev/null +++ b/libopenage/gamestate/activity/xor_event_gate.h @@ -0,0 +1,112 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "error/error.h" +#include "log/message.h" + +#include "gamestate/activity/node.h" +#include "gamestate/activity/types.h" +#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)>; + + +/** + * Waits for an event to be executed before continuing the control flow. + */ +class XorEventGate : public Node { +public: + /** + * Create a new exclusive event gateway. + * + * @param id Unique identifier for this node. + * @param label Human-readable label (optional). + */ + XorEventGate(node_id_t id, + node_label_t label = "EventGateWay"); + + /** + * Create a new exclusive event gateway. + * + * @param id Unique identifier for this node. + * @param label Human-readable label. + * @param outputs Output nodes. + * @param primers Event primers for each output node. + */ + XorEventGate(node_id_t id, + node_label_t label, + const std::vector> &outputs, + const std::map &primers); + + virtual ~XorEventGate() = default; + + inline node_t get_type() const override { + return node_t::XOR_EVENT_GATE; + } + + /** + * Add an output node. + * + * @param output Output node. + * @param primer Creation function for the event associated with the output node. + */ + void add_output(const std::shared_ptr &output, + const event_primer_t &primer); + + /** + * Get the output->event primer mappings. + * + * @return Event primer functions for each output node. + */ + const std::map &get_primers() const; + +private: + /** + * Maps output node IDs to event primer functions. + * + * Events are created and registered on the event loop when the node is visited. + */ + std::map primers; +}; + +} // namespace activity +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/activity/xor_gate.cpp b/libopenage/gamestate/activity/xor_gate.cpp new file mode 100644 index 0000000000..5d37908f8b --- /dev/null +++ b/libopenage/gamestate/activity/xor_gate.cpp @@ -0,0 +1,58 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "xor_gate.h" + +#include + + +namespace openage::gamestate::activity { + +XorGate::XorGate(node_id_t id, + node_label_t label) : + Node{id, label, {}}, + conditions{}, + default_node{nullptr} { +} + +XorGate::XorGate(node_id_t id, + node_label_t label, + const std::vector> &outputs, + const std::vector &conditions, + const std::shared_ptr &default_node) : + Node{id, label, outputs}, + conditions{}, + default_node{default_node} { + if (conditions.size() != outputs.size()) [[unlikely]] { + throw Error{MSG(err) << "XorGate " << this->str() << " has " << outputs.size() + << " outputs but " << conditions.size() << " conditions"}; + } + + for (size_t i = 0; i < conditions.size(); ++i) { + this->conditions.emplace(outputs[i]->get_id(), conditions[i]); + } +} + +void XorGate::add_output(const std::shared_ptr &output, + const condition_t condition_func) { + this->outputs.emplace(output->get_id(), output); + this->conditions.emplace(output->get_id(), condition_func); +} + +const std::map &XorGate::get_conditions() const { + return this->conditions; +} + +const std::shared_ptr &XorGate::get_default() const { + return this->default_node; +} + +void XorGate::set_default(const std::shared_ptr &node) { + if (this->default_node != nullptr) { + throw Error{MSG(err) << "XorGate " << 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_gate.h b/libopenage/gamestate/activity/xor_gate.h new file mode 100644 index 0000000000..0e85a4bf21 --- /dev/null +++ b/libopenage/gamestate/activity/xor_gate.h @@ -0,0 +1,119 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include +#include + +#include "error/error.h" +#include "log/message.h" + +#include "gamestate/activity/node.h" +#include "gamestate/activity/types.h" +#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 &)>; + + +/** + * Chooses one of its output nodes based on conditions. + */ +class XorGate : public Node { +public: + /** + * Creates a new condition node. + * + * @param id Unique identifier of the node. + * @param label Label of the node (optional). + */ + XorGate(node_id_t id, + node_label_t label = "ExclusiveGateway"); + + /** + * Creates a new condition node. + * + * @param id Unique identifier of the node. + * @param label Label of the node. + * @param outputs Output nodes. + * @param conditions Conditions for each output node. + * @param default_node Default output node. Chosen if no condition is true. + */ + XorGate(node_id_t id, + node_label_t label, + const std::vector> &outputs, + const std::vector &conditions, + const std::shared_ptr &default_node); + + virtual ~XorGate() = default; + + inline node_t get_type() const override { + return node_t::XOR_GATE; + } + + /** + * Add an output node. + * + * @param output Output node. + * @param condition_func 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); + + /** + * Get the output->condition mappings. + * + * @return Conditions for each output node. + */ + const std::map &get_conditions() 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 no condition is true. + * + * @param node Default output node. + */ + void set_default(const std::shared_ptr &node); + +private: + /** + * Maps output node IDs to condition functions. + * + * Conditions are checked in order they appear in the map. + */ + std::map conditions; + + /** + * Default output node. Chosen if no condition is true. + */ + std::shared_ptr default_node; +}; + +} // namespace activity +} // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/xor_node.cpp b/libopenage/gamestate/activity/xor_node.cpp deleted file mode 100644 index 644362f877..0000000000 --- a/libopenage/gamestate/activity/xor_node.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. - -#include "xor_node.h" - -#include - - -namespace openage::gamestate::activity { - -XorGate::XorGate(node_id id, - node_label label, - const std::vector> &outputs, - condition_func_t condition_func) : - Node{id, label, outputs}, - condition_func{condition_func} { -} - -void XorGate::add_output(const std::shared_ptr &output) { - this->outputs.emplace(output->get_id(), output); -} - -void XorGate::set_condition_func(condition_func_t condition_func) { - this->condition_func = condition_func; -} - -condition_func_t XorGate::get_condition_func() const { - return this->condition_func; -} - -} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/xor_node.h b/libopenage/gamestate/activity/xor_node.h deleted file mode 100644 index 39efca6209..0000000000 --- a/libopenage/gamestate/activity/xor_node.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "error/error.h" -#include "log/message.h" - -#include "gamestate/activity/node.h" -#include "gamestate/activity/types.h" -#include "time/time.h" - - -namespace openage::gamestate { -class GameEntity; - -namespace activity { - -using condition_func_t = std::function &)>; - -static const condition_func_t no_condition = [](const time::time_t &, - const std::shared_ptr &) -> node_id { - throw Error{MSG(err) << "No condition function set."}; -}; - - -/** - * Chooses one of its output nodes based on a condition. - */ -class XorGate : public Node { -public: - /** - * Creates a new condition node. - * - * @param id Unique identifier of the node. - * @param label Label of the node (optional). - * @param outputs Output nodes (can be set later). - * @param condition_func Function that determines which output node is chosen (can be set later). - * This must be a valid node ID of one of the output nodes. - */ - XorGate(node_id id, - node_label label = "ExclusiveGateway", - const std::vector> &outputs = {}, - condition_func_t condition_func = no_condition); - virtual ~XorGate() = default; - - inline node_t get_type() const override { - return node_t::XOR_GATE; - } - - /** - * Add an output node. - * - * @param output Output node. - */ - void add_output(const std::shared_ptr &output) override; - - /** - * Set the function that determines which output node is chosen. - * - * @param condition_func Function that determines which output node is chosen. - * This must be a valid node ID of one of the output nodes. - */ - void set_condition_func(condition_func_t condition_func); - - /** - * Get the function that determines which output node is chosen. - * - * @return Function that determines which output node is chosen. - */ - condition_func_t get_condition_func() const; - -private: - /** - * Determines which output node is chosen. - */ - condition_func_t condition_func; -}; - -} // namespace activity -} // namespace openage::gamestate diff --git a/libopenage/gamestate/api/CMakeLists.txt b/libopenage/gamestate/api/CMakeLists.txt index f079f11be3..f3ad8d7b40 100644 --- a/libopenage/gamestate/api/CMakeLists.txt +++ b/libopenage/gamestate/api/CMakeLists.txt @@ -1,5 +1,6 @@ add_sources(libopenage ability.cpp + activity.cpp animation.cpp definitions.cpp patch.cpp diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp new file mode 100644 index 0000000000..edd596abe8 --- /dev/null +++ b/libopenage/gamestate/api/activity.cpp @@ -0,0 +1,115 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "activity.h" + +#include "gamestate/api/definitions.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"; +} + +nyan::Object APIActivity::get_start(const nyan::Object &activity) { + auto obj_value = activity.get("Activity.start"); + + std::shared_ptr db_view = activity.get_view(); + return db_view->get_object(obj_value->get_name()); +} + + +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"; +} + +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); +} + +std::vector APIActivityNode::get_next(const nyan::Object &node) { + switch (APIActivityNode::get_type(node)) { + // 0 next nodes + case activity::node_t::END: { + return {}; + } + // 1 next node + case activity::node_t::TASK_SYSTEM: { + auto next = node.get("Ability.next"); + std::shared_ptr db_view = node.get_view(); + return {db_view->get_object(next->get_name())}; + } + case activity::node_t::START: { + auto next = node.get("Start.next"); + std::shared_ptr db_view = node.get_view(); + return {db_view->get_object(next->get_name())}; + } + // 1+ next nodes + case activity::node_t::XOR_GATE: { + auto conditions = node.get("XORGate.next"); + std::shared_ptr db_view = node.get_view(); + + std::vector next_nodes; + for (auto &condition : conditions->get()) { + auto condition_value = std::dynamic_pointer_cast(condition.get_ptr()); + auto condition_obj = db_view->get_object(condition_value->get_name()); + + auto next_node_value = condition_obj.get("Condition.next"); + next_nodes.push_back(db_view->get_object(next_node_value->get_name())); + } + + auto default_next = node.get("XORGate.default"); + next_nodes.push_back(db_view->get_object(default_next->get_name())); + + return next_nodes; + } + case activity::node_t::XOR_EVENT_GATE: { + auto next = node.get("XOREventGate.next"); + std::shared_ptr db_view = node.get_view(); + + std::vector next_nodes; + for (auto &next_node : next->get()) { + 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 activity node type."); + } +} + +system::system_id_t APIActivityNode::get_system_id(const nyan::Object &ability_node) { + auto ability = ability_node.get("Ability.ability"); + + if (not ACTIVITY_TASK_SYSTEM_DEFS.contains(ability->get_name())) [[unlikely]] { + throw Error(MSG(err) << "Ability '" << ability->get_name() << "' has no associated system defined."); + } + + return ACTIVITY_TASK_SYSTEM_DEFS.get(ability->get_name()); +} + +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"; +} + +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); +} + +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"; +} + +activity::event_primer_t APIActivityEvent::get_primer(const nyan::Object &event) { + return ACTIVITY_EVENT_PRIMERS.get(event.get_name()); +} + +} // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/activity.h b/libopenage/gamestate/api/activity.h new file mode 100644 index 0000000000..f1736d8d1c --- /dev/null +++ b/libopenage/gamestate/api/activity.h @@ -0,0 +1,138 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include + +#include "gamestate/activity/types.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" +#include "gamestate/system/types.h" + + +namespace openage::gamestate { + +namespace api { + +/** + * Helper class for creating Activity objects from the nyan API. + */ +class APIActivity { +public: + /** + * Check if a nyan object is an Activity (type == \p engine.util.activity.Activity). + * + * @param obj nyan object. + * + * @return true if the object is an activity, else false. + */ + static bool is_activity(const nyan::Object &obj); + + /** + * Get the start node of an activity. + * + * @param activity nyan object. + * + * @return nyan object handle of the start node. + */ + static nyan::Object get_start(const nyan::Object &activity); +}; + +/** + * Helper class for creating Activity node objects from the nyan API. + */ +class APIActivityNode { +public: + /** + * Check if a nyan object is a node (type == \p engine.util.activity.node.Node). + * + * @param obj nyan object. + * + * @return true if the object is a node, else false. + */ + static bool is_node(const nyan::Object &obj); + + /** + * Get the type of a node. + * + * @param node nyan object. + * + * @return Type of the node. + */ + static activity::node_t get_type(const nyan::Object &node); + + /** + * Get the next nodes of a node. + * + * The number of next nodes depends on the type of the node and can range + * from 0 (end nodes) to n (gateways). + * + * @param node nyan object. + * + * @return nyan object handles of the next nodes. + */ + static std::vector get_next(const nyan::Object &node); + + /** + * Get the system id of an Ability node. + * + * @param node nyan object. + * + * @return System ID of the node. + */ + static system::system_id_t get_system_id(const nyan::Object &ability_node); +}; + +/** + * Helper class for creating Activity condition objects from the nyan API. + */ +class APIActivityCondition { +public: + /** + * Check if a nyan object is a condition (type == \p engine.util.activity.condition.Condition). + * + * @param obj nyan object. + * + * @return true if the object is a condition, else false. + */ + static bool is_condition(const nyan::Object &obj); + + /** + * Get the condition function for a condition. + * + * @param condition nyan object. + * + * @return Condition function. + */ + static activity::condition_t get_condition(const nyan::Object &condition); +}; + +/** + * Helper class for creating Activity event objects from the nyan API. + */ +class APIActivityEvent { +public: + /** + * Check if a nyan object is an event (type == \p engine.util.activity.event.Event). + * + * @param obj nyan object. + * + * @return true if the object is an event, else false. + */ + static bool is_event(const nyan::Object &obj); + + /** + * Get the primer function for an event type. + * + * @param event nyan object. + * + * @return Event primer function. + */ + static activity::event_primer_t get_primer(const nyan::Object &event); +}; + + +} // namespace api +} // namespace openage::gamestate diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index d06553c89e..f982571f94 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -8,12 +8,22 @@ #include #include "datastructure/constexpr_map.h" +#include "gamestate/activity/condition/command_in_queue.h" +#include "gamestate/activity/condition/next_command.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/api/types.h" +#include "gamestate/system/types.h" namespace openage::gamestate::api { -/** Maps internal ability types to nyan API values **/ +/** + * Maps internal ability types to nyan API values. + */ static const auto ABILITY_DEFS = datastructure::create_const_map( std::pair(ability_t::IDLE, nyan::ValueHolder(std::make_shared("engine.ability.type.Idle"))), @@ -24,8 +34,9 @@ static const auto ABILITY_DEFS = datastructure::create_const_map("engine.ability.type.Turn")))); - -/** Maps internal property types to nyan API values **/ +/** + * Maps internal property types to nyan API values. + */ static const auto ABILITY_PROPERTY_DEFS = datastructure::create_const_map( std::pair(ability_property_t::ANIMATED, nyan::ValueHolder(std::make_shared("engine.ability.property.type.Animated"))), @@ -40,6 +51,54 @@ static const auto ABILITY_PROPERTY_DEFS = datastructure::create_const_map("engine.ability.property.type.Lock")))); +/** + * Maps API activity node types to engine node types. + */ +static const auto ACTIVITY_NODE_DEFS = 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.XORGate", + activity::node_t::XOR_GATE), + std::pair("engine.util.activity.node.type.XOREventGate", + activity::node_t::XOR_EVENT_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( + std::pair("engine.ability.type.Idle", + system::system_id_t::IDLE), + std::pair("engine.ability.type.Move", + system::system_id_t::MOVE_COMMAND)); + +/** + * Maps API activity condition types to engine condition types. + */ +static const auto ACTIVITY_CONDITIONS = 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))); + +static const auto ACTIVITY_EVENT_PRIMERS = 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", + std::function(gamestate::activity::primer_wait)), + std::pair("engine.util.activity.event.type.WaitAbility", + std::function(gamestate::activity::primer_wait))); + +/** + * Maps internal patch property types to nyan API values. + */ static const auto PATCH_PROPERTY_DEFS = datastructure::create_const_map( std::pair(patch_property_t::DIPLOMATIC, nyan::ValueHolder(std::make_shared("engine.patch.property.type.Diplomatic")))); diff --git a/libopenage/gamestate/component/internal/command_queue.cpp b/libopenage/gamestate/component/internal/command_queue.cpp index 6896f63cdf..4c6963a75d 100644 --- a/libopenage/gamestate/component/internal/command_queue.cpp +++ b/libopenage/gamestate/component/internal/command_queue.cpp @@ -22,11 +22,11 @@ void CommandQueue::add_command(const time::time_t &time, this->command_queue.insert(time, command); } -const curve::Queue> &CommandQueue::get_queue() const { +curve::Queue> &CommandQueue::get_queue() { return this->command_queue; } -std::shared_ptr CommandQueue::pop_command(const time::time_t &time) { +const std::shared_ptr CommandQueue::pop_command(const time::time_t &time) { return this->command_queue.pop_front(time); } diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index 6c16d5cbc7..ea0c26502a 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -44,7 +44,7 @@ class CommandQueue : public InternalComponent { * * @return Command queue. */ - const curve::Queue> &get_queue() const; + curve::Queue> &get_queue(); /** * Get the command in the front of the queue. @@ -53,7 +53,7 @@ class CommandQueue : public InternalComponent { * * @return Command in the front of the queue or nullptr if the queue is empty. */ - std::shared_ptr pop_command(const time::time_t &time); + const std::shared_ptr pop_command(const time::time_t &time); private: /** diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index f6c3d6f660..831527c1ec 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -14,11 +14,15 @@ #include "curve/queue.h" #include "event/event_loop.h" #include "gamestate/activity/activity.h" +#include "gamestate/activity/condition/command_in_queue.h" #include "gamestate/activity/end_node.h" -#include "gamestate/activity/event_node.h" +#include "gamestate/activity/event/command_in_queue.h" +#include "gamestate/activity/event/wait.h" #include "gamestate/activity/start_node.h" #include "gamestate/activity/task_system_node.h" -#include "gamestate/activity/xor_node.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" +#include "gamestate/api/activity.h" #include "gamestate/component/api/idle.h" #include "gamestate/component/api/live.h" #include "gamestate/component/api/move.h" @@ -38,7 +42,6 @@ #include "time/time.h" #include "util/fixed_point.h" - namespace openage::gamestate { /** @@ -47,19 +50,9 @@ namespace openage::gamestate { * The activity is as follows: * |------------------------------------------------------| * | v - * Start -> Idle -> Condition -> Wait for command -> Move -> Wait for move -> End - * ^ | - * |------------------------------------------------------| - * - * TODO: Should be: - * |----------------------------------------------------------------------| - * | v - * Start -> Idle -> -> Condition -> Wait for command <-> Condition -> Move -> Wait or command -> End - * ^ |^ | - * |---------------------------------------------||---------------------| - * (new condition in the middle: check if there is a command, if not go back to wait for command) - * (new node: go back to node 1 if there is no command) - * (node 5: wait for a command OR for a the wait time) + * Start -> Idle -> Condition -> Condition -> Wait for command -> Move -> Wait for move -> End + * ^ | + * |----------------------------------------------------------------| * * TODO: Replace with config */ @@ -67,89 +60,48 @@ std::shared_ptr create_test_activity() { auto start = std::make_shared(0); auto idle = std::make_shared(1, "Idle"); auto condition_moveable = std::make_shared(2); - auto wait_for_command = std::make_shared(3); - auto condition_command = std::make_shared(4); + auto condition_command = std::make_shared(3); + auto wait_for_command = std::make_shared(4); auto move = std::make_shared(5, "Move"); auto wait_for_move = std::make_shared(6); auto end = std::make_shared(7); start->add_output(idle); + // idle after start idle->add_output(condition_moveable); idle->set_system_id(system::system_id_t::IDLE); - condition_moveable->add_output(wait_for_command); - condition_moveable->add_output(end); - condition_moveable->set_condition_func([&](const time::time_t & /* time */, - const std::shared_ptr &entity) { - if (entity->has_component(component::component_t::MOVE)) { - return 3; // wait_for_command->get_id(); - } + // branch 1: check if the entity is moveable + activity::condition_t command_branch = [&](const time::time_t & /* time */, + const std::shared_ptr &entity) { + return entity->has_component(component::component_t::MOVE); + }; + condition_moveable->add_output(condition_command, command_branch); - return 7; // end->get_id(); - }); - - wait_for_command->add_output(move); - wait_for_command->set_primer_func([](const time::time_t & /* time */, - const std::shared_ptr &entity, - const std::shared_ptr &loop, - const std::shared_ptr &state) { - auto ev = loop->create_event("game.process_command", - entity->get_manager(), - state, - // event is not executed until a command is available - std::numeric_limits::max()); - auto entity_queue = std::dynamic_pointer_cast( - entity->get_component(component::component_t::COMMANDQUEUE)); - auto &queue = const_cast> &>(entity_queue->get_queue()); - queue.add_dependent(ev); - - return activity::event_store_t{ev}; - }); - wait_for_command->set_next_func([](const time::time_t &time, - const std::shared_ptr &entity, - const std::shared_ptr &, - const std::shared_ptr &) { - auto entity_queue = std::dynamic_pointer_cast( - entity->get_component(component::component_t::COMMANDQUEUE)); - auto &queue = entity_queue->get_queue(); - - if (queue.empty(time)) { - throw Error{ERR << "Command queue is empty"}; - } - auto &com = queue.front(time); - if (com->get_type() == component::command::command_t::MOVE) { - return 5; // move->get_id(); - } + // 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); - throw Error{ERR << "Unknown command type"}; - }); + // default: if there is no command, wait for a command + condition_command->set_default(wait_for_command); + // wait for a command event + wait_for_command->add_output(move, gamestate::activity::primer_command_in_queue); + + // move move->add_output(wait_for_move); move->set_system_id(system::system_id_t::MOVE_COMMAND); - wait_for_move->add_output(idle); - wait_for_move->add_output(condition_command); - wait_for_move->add_output(end); - wait_for_move->set_primer_func([](const time::time_t &time, - const std::shared_ptr &entity, - const std::shared_ptr &loop, - const std::shared_ptr &state) { - auto ev = loop->create_event("game.wait", - entity->get_manager(), - state, - time); - - return activity::event_store_t{ev}; - }); - wait_for_move->set_next_func([&](const time::time_t &, - const std::shared_ptr &, - const std::shared_ptr &, - const std::shared_ptr &) { - return 1; // idle->get_id(); - }); - - return std::make_shared(0, "test", start); + // branch 1: wait for move event to finish + wait_for_move->add_output(idle, gamestate::activity::primer_wait); + + // branch 2: wait for a new command event + wait_for_move->add_output(move, gamestate::activity::primer_command_in_queue); + + return std::make_shared(0, start, "test"); } EntityFactory::EntityFactory() : @@ -210,6 +162,7 @@ void EntityFactory::init_components(const std::shared_ptrget_object(nyan_entity); nyan::set_t abilities = nyan_obj.get_set("GameEntity.abilities"); + std::optional activity_ability; for (const auto &ability_val : abilities) { auto ability_fqon = std::dynamic_pointer_cast(ability_val.get_ptr())->get_name(); auto ability_obj = owner_db_view->get_object(ability_fqon); @@ -238,7 +191,7 @@ void EntityFactory::init_components(const std::shared_ptradd_attribute(std::numeric_limits::min(), + live->add_attribute(time::TIME_MIN, attribute.get_name(), std::make_shared>(loop, 0, @@ -247,11 +200,157 @@ void EntityFactory::init_components(const std::shared_ptr(loop, create_test_activity()); + entity->add_component(activity); + } +} + +void EntityFactory::init_activity(const std::shared_ptr &loop, + const std::shared_ptr &owner_db_view, + const std::shared_ptr &entity, + const nyan::Object &ability) { + nyan::Object graph = ability.get_object("Activity.graph"); + + // Check if the activity is already exists in the cache + if (this->activity_cache.contains(graph.get_name())) { + auto activity = this->activity_cache.at(graph.get_name()); + auto component = std::make_shared(loop, activity); + entity->add_component(component); + + return; + } + + auto start_obj = api::APIActivity::get_start(graph); + + size_t node_id = 0; + + std::deque nyan_nodes; + std::unordered_map> node_id_map{}; + std::unordered_map visited{}; + std::shared_ptr start_node; + + // First pass: create all nodes using breadth-first search + nyan_nodes.push_back(start_obj); + while (!nyan_nodes.empty()) { + auto node = nyan_nodes.front(); + nyan_nodes.pop_front(); + + if (visited.contains(node.get_name())) { + continue; + } + + // Create the node + switch (api::APIActivityNode::get_type(node)) { + case activity::node_t::END: + break; + case activity::node_t::START: + start_node = std::make_shared(node_id); + node_id_map[node_id] = start_node; + break; + case activity::node_t::TASK_SYSTEM: { + auto task_node = std::make_shared(node_id); + task_node->set_system_id(api::APIActivityNode::get_system_id(node)); + node_id_map[node_id] = task_node; + break; + } + case activity::node_t::XOR_GATE: + node_id_map[node_id] = std::make_shared(node_id); + break; + case activity::node_t::XOR_EVENT_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()}; + } + + // Get the node's outputs + auto next_nodes = api::APIActivityNode::get_next(node); + nyan_nodes.insert(nyan_nodes.end(), next_nodes.begin(), next_nodes.end()); + + visited.insert({node.get_name(), node_id}); + node_id++; + } + + // Second pass: connect the nodes + for (const auto ¤t_node : visited) { + auto nyan_node = owner_db_view->get_object(current_node.first); + auto activity_node = node_id_map[current_node.second]; + + switch (activity_node->get_type()) { + case activity::node_t::END: + break; + case activity::node_t::START: { + auto start = std::static_pointer_cast(activity_node); + auto output_fqon = nyan_node.get("Start.next")->get_name(); + auto output_id = visited[output_fqon]; + auto output_node = node_id_map[output_id]; + start->add_output(output_node); + break; + } + case activity::node_t::TASK_SYSTEM: { + auto task_system = std::static_pointer_cast(activity_node); + auto output_fqon = nyan_node.get("Ability.next")->get_name(); + auto output_id = visited[output_fqon]; + auto output_node = node_id_map[output_id]; + task_system->add_output(output_node); + break; + } + case activity::node_t::XOR_GATE: { + auto xor_gate = std::static_pointer_cast(activity_node); + auto conditions = nyan_node.get("XORGate.next"); + for (auto &condition : conditions->get()) { + auto condition_value = std::dynamic_pointer_cast(condition.get_ptr()); + auto condition_obj = owner_db_view->get_object(condition_value->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)); + } + + auto default_fqon = nyan_node.get("XORGate.default")->get_name(); + auto default_id = visited[default_fqon]; + auto default_node = node_id_map[default_id]; + xor_gate->set_default(default_node); + break; + } + case activity::node_t::XOR_EVENT_GATE: { + auto xor_event_gate = std::static_pointer_cast(activity_node); + auto next = nyan_node.get("XOREventGate.next"); + for (auto &next_node : next->get()) { + auto event_value = std::dynamic_pointer_cast(next_node.first.get_ptr()); + auto event_obj = owner_db_view->get_object(event_value->get_name()); + + auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); + auto next_node_obj = owner_db_view->get_object(next_node_value->get_name()); + + auto output_id = visited[next_node_obj.get_name()]; + auto output_node = node_id_map[output_id]; + + xor_event_gate->add_output(output_node, api::APIActivityEvent::get_primer(event_obj)); + } + break; + } + default: + throw Error{ERR << "Unknown activity node type of node: " << current_node.first}; + } } - // must be initialized after all other components - auto activity = std::make_shared(loop, create_test_activity()); - entity->add_component(activity); + auto activity = std::make_shared(0, start_node, graph.get_name()); + this->activity_cache.insert({graph.get_name(), activity}); + + auto component = std::make_shared(loop, activity); + entity->add_component(component); } entity_id_t EntityFactory::get_next_entity_id() { diff --git a/libopenage/gamestate/entity_factory.h b/libopenage/gamestate/entity_factory.h index 5a369d995e..1a12ece80b 100644 --- a/libopenage/gamestate/entity_factory.h +++ b/libopenage/gamestate/entity_factory.h @@ -21,6 +21,11 @@ class RenderFactory; } namespace gamestate { + +namespace activity { +class Activity; +} // namespace activity + class GameEntity; class GameState; class Player; @@ -92,6 +97,11 @@ class EntityFactory { const std::shared_ptr &entity, const nyan::fqon_t &nyan_entity); + void init_activity(const std::shared_ptr &loop, + const std::shared_ptr &owner_db_view, + const std::shared_ptr &entity, + const nyan::Object &ability); + /** * Get a unique ID for creating a game entity. * @@ -123,6 +133,11 @@ class EntityFactory { // TODO: Cache created game entities. + /** + * Cache for activities. + */ + std::unordered_map> activity_cache; + /** * Mutex for thread safety. */ diff --git a/libopenage/gamestate/event/process_command.cpp b/libopenage/gamestate/event/process_command.cpp index 4783fe89c8..d00762d8c9 100644 --- a/libopenage/gamestate/event/process_command.cpp +++ b/libopenage/gamestate/event/process_command.cpp @@ -19,16 +19,15 @@ void ProcessCommandHandler::invoke(openage::event::EventLoop & /* loop */, const std::shared_ptr &target, const std::shared_ptr & /* state */, const time::time_t &time, - const param_map & /* params */) { + const param_map ¶ms) { auto mgr = std::dynamic_pointer_cast(target); - mgr->run_activity_system(time); + mgr->run_activity_system(time, params); } time::time_t ProcessCommandHandler::predict_invoke_time(const std::shared_ptr & /* target */, - const std::shared_ptr & /* state */, - const time::time_t &at) { + const std::shared_ptr & /* state */, + const time::time_t &at) { return at; } - } // namespace openage::gamestate::event diff --git a/libopenage/gamestate/event/process_command.h b/libopenage/gamestate/event/process_command.h index 8b940dd616..db4fb18157 100644 --- a/libopenage/gamestate/event/process_command.h +++ b/libopenage/gamestate/event/process_command.h @@ -17,8 +17,12 @@ class EventEntity; class State; } // namespace event -namespace gamestate::event { +namespace gamestate { +class GameEntity; +class GameState; + +namespace event { /** * Process a command from a game entity command queue. */ @@ -42,5 +46,6 @@ class ProcessCommandHandler : public openage::event::OnceEventHandler { }; -} // namespace gamestate::event +} // namespace event +} // namespace gamestate } // namespace openage diff --git a/libopenage/gamestate/event/wait.cpp b/libopenage/gamestate/event/wait.cpp index 2d067086fa..eb2e793334 100644 --- a/libopenage/gamestate/event/wait.cpp +++ b/libopenage/gamestate/event/wait.cpp @@ -19,15 +19,16 @@ void WaitHandler::invoke(openage::event::EventLoop & /* loop */, const std::shared_ptr &target, const std::shared_ptr & /* state */, const time::time_t &time, - const param_map & /* params */) { + const param_map ¶ms) { auto mgr = std::dynamic_pointer_cast(target); - mgr->run_activity_system(time); + mgr->run_activity_system(time, params); } time::time_t WaitHandler::predict_invoke_time(const std::shared_ptr & /* target */, - const std::shared_ptr & /* state */, - const time::time_t &at) { + const std::shared_ptr & /* state */, + const time::time_t &at) { return at; } + } // namespace openage::gamestate::event diff --git a/libopenage/gamestate/event/wait.h b/libopenage/gamestate/event/wait.h index 2c0d9732f2..1c74015984 100644 --- a/libopenage/gamestate/event/wait.h +++ b/libopenage/gamestate/event/wait.h @@ -17,7 +17,11 @@ class EventEntity; class State; } // namespace event -namespace gamestate::event { +namespace gamestate { +class GameEntity; +class GameState; + +namespace event { /** * Waits until the event is handled and calls back the entity manager. @@ -40,5 +44,7 @@ class WaitHandler : public openage::event::OnceEventHandler { const std::shared_ptr &state, const time::time_t &at) override; }; -} // namespace gamestate::event + +} // namespace event +} // namespace gamestate } // namespace openage diff --git a/libopenage/gamestate/manager.cpp b/libopenage/gamestate/manager.cpp index 9c8f6c1899..f00ccab22c 100644 --- a/libopenage/gamestate/manager.cpp +++ b/libopenage/gamestate/manager.cpp @@ -20,9 +20,10 @@ GameEntityManager::GameEntityManager(const std::shared_ptr &ev_params) { log::log(DBG << "Running activity system for entity " << this->game_entity->get_id()); - system::Activity::advance(this->game_entity, time, this->loop, this->state); + system::Activity::advance(time, this->game_entity, this->loop, this->state, ev_params); } size_t GameEntityManager::id() const { diff --git a/libopenage/gamestate/manager.h b/libopenage/gamestate/manager.h index 6131c94bd2..8d6976c806 100644 --- a/libopenage/gamestate/manager.h +++ b/libopenage/gamestate/manager.h @@ -4,9 +4,11 @@ #include #include +#include #include #include "event/evententity.h" +#include "event/eventhandler.h" #include "time/time.h" @@ -26,7 +28,8 @@ class GameEntityManager : public openage::event::EventEntity { const std::shared_ptr &game_entity); ~GameEntityManager() = default; - void run_activity_system(const time::time_t &time); + void run_activity_system(const time::time_t &time, + const std::optional &ev_params = std::nullopt); size_t id() const override; std::string idstr() const override; diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index 240b5c7c50..49371fba30 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.cpp @@ -9,13 +9,13 @@ #include "error/error.h" #include "log/message.h" -#include "gamestate/activity/event_node.h" #include "gamestate/activity/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_node.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" #include "gamestate/component/internal/activity.h" #include "gamestate/component/types.h" #include "gamestate/game_entity.h" @@ -27,10 +27,11 @@ namespace openage::gamestate::system { -void Activity::advance(const std::shared_ptr &entity, - const time::time_t &start_time, +void Activity::advance(const time::time_t &start_time, + const std::shared_ptr &entity, const std::shared_ptr &loop, - const std::shared_ptr &state) { + const std::shared_ptr &state, + const std::optional &ev_params) { auto activity_component = std::dynamic_pointer_cast( entity->get_component(component::component_t::ACTIVITY)); auto current_node = activity_component->get_node(start_time); @@ -44,10 +45,12 @@ void Activity::advance(const std::shared_ptr &entity, if (current_node->get_type() == activity::node_t::XOR_EVENT_GATE) { // returning to a event gateway means that the event has been triggered // move to the next node here - auto node = std::static_pointer_cast(current_node); - auto event_next = node->get_next_func(); - auto next_id = event_next(start_time, entity, loop, state); - current_node = node->next(next_id); + if (not ev_params.has_value()) { + throw Error{ERR << "XorEventGate: No event parameters given on continue"}; + } + + auto next_id = ev_params.value().get("next"); + current_node = current_node->next(next_id); // cancel all other events that the manager may have been waiting for activity_component->cancel_events(start_time); @@ -82,15 +85,25 @@ void Activity::advance(const std::shared_ptr &entity, } break; case activity::node_t::XOR_GATE: { auto node = std::static_pointer_cast(current_node); - auto condition = node->get_condition_func(); - auto next_id = condition(start_time, entity); + 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)) { + next_id = condition.first; + break; + } + } current_node = node->next(next_id); } break; case activity::node_t::XOR_EVENT_GATE: { auto node = std::static_pointer_cast(current_node); - auto event_primer = node->get_primer_func(); - auto evs = event_primer(start_time + event_wait_time, entity, loop, state); - for (auto &ev : evs) { + auto event_primers = node->get_primers(); + for (auto &primer : event_primers) { + auto ev = primer.second(start_time + event_wait_time, + entity, + loop, + state, + primer.first); activity_component->add_event(ev); } diff --git a/libopenage/gamestate/system/activity.h b/libopenage/gamestate/system/activity.h index d5283fceca..d6c10215fc 100644 --- a/libopenage/gamestate/system/activity.h +++ b/libopenage/gamestate/system/activity.h @@ -3,9 +3,11 @@ #pragma once #include +#include -#include "time/time.h" +#include "event/eventhandler.h" #include "gamestate/system/types.h" +#include "time/time.h" namespace openage { @@ -25,13 +27,14 @@ class Activity { /** * Advance in the activity flow graph of the game entity. * - * @param entity Game entity. * @param start_time Start time of change. + * @param entity Game entity. */ - static void advance(const std::shared_ptr &entity, - const time::time_t &start_time, + static void advance(const time::time_t &start_time, + const std::shared_ptr &entity, const std::shared_ptr &loop, - const std::shared_ptr &state); + const std::shared_ptr &state, + const std::optional &ev_params = std::nullopt); private: /** @@ -44,8 +47,8 @@ class Activity { * @return Runtime of the change in simulation time. */ static const time::time_t handle_subsystem(const std::shared_ptr &entity, - const time::time_t &start_time, - system_id_t system_id); + const time::time_t &start_time, + system_id_t system_id); }; } // namespace system diff --git a/libopenage/gamestate/terrain.cpp b/libopenage/gamestate/terrain.cpp index f31fc648b8..9e40230ffe 100644 --- a/libopenage/gamestate/terrain.cpp +++ b/libopenage/gamestate/terrain.cpp @@ -32,7 +32,7 @@ void Terrain::attach_renderer(const std::shared_ptr &re chunk->get_offset()); chunk->set_render_entity(render_entity); - chunk->render_update(time::time_t::zero()); + chunk->render_update(time::TIME_ZERO); } } diff --git a/libopenage/gamestate/terrain_factory.cpp b/libopenage/gamestate/terrain_factory.cpp index 3a46773429..7276d73d57 100644 --- a/libopenage/gamestate/terrain_factory.cpp +++ b/libopenage/gamestate/terrain_factory.cpp @@ -131,7 +131,7 @@ std::shared_ptr TerrainFactory::add_chunk(const std::shared_ptrrender_factory->add_terrain_render_entity(size, offset); chunk->set_render_entity(render_entity); - chunk->render_update(time::time_t::zero(), + chunk->render_update(time::TIME_ZERO, test_texture_path); } diff --git a/libopenage/main/demo/pong/aicontroller.cpp b/libopenage/main/demo/pong/aicontroller.cpp index bdd1b7df3a..fd635d2a21 100644 --- a/libopenage/main/demo/pong/aicontroller.cpp +++ b/libopenage/main/demo/pong/aicontroller.cpp @@ -34,7 +34,7 @@ std::vector get_ai_inputs(const std::shared_ptr &player, time::time_t ty_hit = 0, tx_hit = 0; if (speed[0] == 0) { - tx_hit = std::numeric_limits::max(); + tx_hit = time::TIME_MAX; } else if (speed[0] > 0) { tx_hit = time::time_t::from_double((area_width - ball_pos[0]) / speed[0]); @@ -44,7 +44,7 @@ std::vector get_ai_inputs(const std::shared_ptr &player, } if (speed[1] == 0) { - ty_hit = std::numeric_limits::max(); + ty_hit = time::TIME_MAX; } else if (speed[1] > 0) { ty_hit = time::time_t::from_double((area_height - ball_pos[1]) / speed[1]); diff --git a/libopenage/main/demo/pong/physics.cpp b/libopenage/main/demo/pong/physics.cpp index 72a1f65c96..e2aa12e28e 100644 --- a/libopenage/main/demo/pong/physics.cpp +++ b/libopenage/main/demo/pong/physics.cpp @@ -75,7 +75,7 @@ class BallReflectWall : public event::DependencyEventHandler { auto screen_size = state->area_size->get(now); if (speed[1] == 0) { - return std::numeric_limits::max(); + return time::TIME_MAX; } time::time_t ty = 0; @@ -199,7 +199,7 @@ class BallReflectPanel : public event::DependencyEventHandler { auto screen_size = state->area_size->get(now); if (speed[0] == 0) - return std::numeric_limits::max(); + return time::TIME_MAX; time::time_t ty = 0; diff --git a/libopenage/time/time.h b/libopenage/time/time.h index a6851f679c..ceac0c6365 100644 --- a/libopenage/time/time.h +++ b/libopenage/time/time.h @@ -15,4 +15,19 @@ namespace openage::time { */ using time_t = util::FixedPoint; +/** + * Minimum time value. + */ +static constexpr time_t TIME_MIN = std::numeric_limits::min(); + +/** + * Maximum time value. + */ +static constexpr time_t TIME_MAX = std::numeric_limits::max(); + +/** + * Zero time value (start of simulation). + */ +static constexpr time_t TIME_ZERO = time_t::zero(); + } // namespace openage::time diff --git a/openage/convert/entity_object/export/formats/modpack_info.py b/openage/convert/entity_object/export/formats/modpack_info.py index 1a6017ce4e..0df5f220ce 100644 --- a/openage/convert/entity_object/export/formats/modpack_info.py +++ b/openage/convert/entity_object/export/formats/modpack_info.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-instance-attributes,too-many-arguments @@ -153,7 +153,7 @@ def add_dependency(self, modpack_id: str) -> None: def set_info( self, packagename: str, - version: str, + modpack_version: str, versionstr: str = None, repo: str = None, alias: str = None, @@ -168,9 +168,9 @@ def set_info( :param packagename: Name of the modpack. :type packagename: str - :param version: Internal version number. Must have semver format. - :type version: str - :param versionstr: Human-readable version number. + :param modpack_version: Internal version number. Must have semver format. + :type modpack_version: str + :param versionstr: Human-readable version number. Can be anything. :type versionstr: str :param repo: Name of the repo where the package is hosted. :type repo: str @@ -188,7 +188,7 @@ def set_info( :type licenses: list """ self.packagename = packagename - self.version = version + self.version = modpack_version if versionstr: self.extra_info["versionstr"] = versionstr diff --git a/openage/convert/processor/conversion/aoc/ability_subprocessor.py b/openage/convert/processor/conversion/aoc/ability_subprocessor.py index 8e53371b2f..ffa0a98e57 100644 --- a/openage/convert/processor/conversion/aoc/ability_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/ability_subprocessor.py @@ -302,6 +302,45 @@ def apply_continuous_effect_ability( return ability_forward_ref + @staticmethod + def activity_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Activity ability to a line. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :returns: The forward reference for the ability. + :rtype: ...dataformat.forward_ref.ForwardRef + """ + current_unit_id = line.get_head_unit_id() + + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.Activity" + ability_raw_api_object = RawAPIObject(ability_ref, "Activity", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Activity") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # activity graph + if isinstance(line, GenieUnitLineGroup): + activity = dataset.pregen_nyan_objects["util.activity.types.Unit"].get_nyan_object() + + else: + activity = dataset.pregen_nyan_objects["util.activity.types.Default"].get_nyan_object() + + ability_raw_api_object.add_raw_member("graph", activity, "engine.ability.type.Activity") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref + @staticmethod def apply_discrete_effect_ability( line: GenieGameEntityGroup, @@ -614,7 +653,7 @@ def apply_discrete_effect_ability( return ability_forward_ref @staticmethod - def attribute_change_tracker_ability(line) -> ForwardRef: + def attribute_change_tracker_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the AttributeChangeTracker ability to a line. diff --git a/openage/convert/processor/conversion/aoc/modpack_subprocessor.py b/openage/convert/processor/conversion/aoc/modpack_subprocessor.py index 8d54d68fed..1c823c3ba6 100644 --- a/openage/convert/processor/conversion/aoc/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/modpack_subprocessor.py @@ -44,7 +44,7 @@ def _get_aoe2_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("aoe2_base", "0.5", versionstr="1.0c", repo="openage") + mod_def.set_info("aoe2_base", "0.5.1", versionstr="1.0c", repo="openage") mod_def.add_include("data/**") @@ -151,6 +151,11 @@ def _set_static_aliases(modpack: Modpack, import_tree: ImportTree) -> None: 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" diff --git a/openage/convert/processor/conversion/aoc/nyan_subprocessor.py b/openage/convert/processor/conversion/aoc/nyan_subprocessor.py index b519412ca3..d280b5d9c1 100644 --- a/openage/convert/processor/conversion/aoc/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/nyan_subprocessor.py @@ -219,6 +219,7 @@ def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: # ======================================================================= 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)) diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index fe6151ea20..db92c0fa8a 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -35,6 +35,7 @@ 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) @@ -62,6 +63,297 @@ 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, diff --git a/openage/convert/processor/conversion/aoc_demo/modpack_subprocessor.py b/openage/convert/processor/conversion/aoc_demo/modpack_subprocessor.py index ffd171214e..8ae5c83705 100644 --- a/openage/convert/processor/conversion/aoc_demo/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/aoc_demo/modpack_subprocessor.py @@ -40,7 +40,7 @@ def _get_demo_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("trial_base", "0.5", versionstr="Trial", repo="openage") + mod_def.set_info("trial_base", "0.5.1", versionstr="Trial", repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/de1/modpack_subprocessor.py b/openage/convert/processor/conversion/de1/modpack_subprocessor.py index 9cc3f5a95d..1a431e81d2 100644 --- a/openage/convert/processor/conversion/de1/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/de1/modpack_subprocessor.py @@ -40,7 +40,7 @@ def _get_aoe1_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("de1_base", "0.5", versionstr="1.0a", repo="openage") + mod_def.set_info("de1_base", "0.5.1", versionstr="1.0a", repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/de2/modpack_subprocessor.py b/openage/convert/processor/conversion/de2/modpack_subprocessor.py index 22d9d220e2..810ff96752 100644 --- a/openage/convert/processor/conversion/de2/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/de2/modpack_subprocessor.py @@ -40,7 +40,7 @@ def _get_aoe2_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("de2_base", "0.5", versionstr="1.0c", repo="openage") + mod_def.set_info("de2_base", "0.5.1", versionstr="1.0c", repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/de2/nyan_subprocessor.py b/openage/convert/processor/conversion/de2/nyan_subprocessor.py index b8f40000ef..242f67e7cd 100644 --- a/openage/convert/processor/conversion/de2/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/de2/nyan_subprocessor.py @@ -219,6 +219,7 @@ def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: # ======================================================================= 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)) diff --git a/openage/convert/processor/conversion/hd/modpack_subprocessor.py b/openage/convert/processor/conversion/hd/modpack_subprocessor.py index 50e77ba711..8be52ac744 100644 --- a/openage/convert/processor/conversion/hd/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/hd/modpack_subprocessor.py @@ -40,7 +40,7 @@ def _get_aoe2_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("hd_base", "0.5", versionstr="5.8", repo="openage") + mod_def.set_info("hd_base", "0.5.1", versionstr="5.8", repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/ror/modpack_subprocessor.py b/openage/convert/processor/conversion/ror/modpack_subprocessor.py index dfe485b003..ff7746e861 100644 --- a/openage/convert/processor/conversion/ror/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/ror/modpack_subprocessor.py @@ -41,7 +41,7 @@ def _get_aoe1_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("aoe1_base", "0.5", versionstr="1.0a", repo="openage") + mod_def.set_info("aoe1_base", "0.5.1", versionstr="1.0a", repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/ror/nyan_subprocessor.py b/openage/convert/processor/conversion/ror/nyan_subprocessor.py index 75262402ae..a06a86e1dc 100644 --- a/openage/convert/processor/conversion/ror/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/ror/nyan_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. +# Copyright 2020-2023 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches # @@ -209,6 +209,7 @@ def unit_line_to_game_entity(unit_line): # ======================================================================= 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)) diff --git a/openage/convert/processor/conversion/ror/pregen_subprocessor.py b/openage/convert/processor/conversion/ror/pregen_subprocessor.py index 4bb4eb0ad1..271110bedc 100644 --- a/openage/convert/processor/conversion/ror/pregen_subprocessor.py +++ b/openage/convert/processor/conversion/ror/pregen_subprocessor.py @@ -32,6 +32,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: # Stores pregenerated raw API objects as a container pregen_converter_group = ConverterObjectGroup("pregen") + AoCPregenSubprocessor.generate_activities(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_attributes(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_diplomatic_stances(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_entity_types(full_data_set, pregen_converter_group) diff --git a/openage/convert/processor/conversion/swgbcc/modpack_subprocessor.py b/openage/convert/processor/conversion/swgbcc/modpack_subprocessor.py index cef278f9eb..5a69a47516 100644 --- a/openage/convert/processor/conversion/swgbcc/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/modpack_subprocessor.py @@ -40,7 +40,7 @@ def _get_swgb_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("swgb_base", "0.5", versionstr="1.1-gog4", repo="openage") + mod_def.set_info("swgb_base", "0.5.1", versionstr="1.1-gog4", repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py b/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py index b48b15d309..9fd2c52242 100644 --- a/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py @@ -217,6 +217,7 @@ def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: # ======================================================================= 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)) diff --git a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py index be0d20e705..a9114648c7 100644 --- a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py @@ -37,6 +37,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: # Stores pregenerated raw API objects as a container pregen_converter_group = ConverterObjectGroup("pregen") + AoCPregenSubprocessor.generate_activities(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_attributes(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_diplomatic_stances(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_team_property(full_data_set, pregen_converter_group) diff --git a/openage/convert/service/init/api_export_required.py b/openage/convert/service/init/api_export_required.py index 61b8f5ba4d..105527e176 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.4.0" +CURRENT_API_VERSION = "0.4.1" 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 067c4d1065..24bd7c10ef 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -111,6 +111,13 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.ability.type.Activity + parents = [api_objects["engine.ability.Ability"]] + nyan_object = NyanObject("Activity", parents) + fqon = "engine.ability.type.Activity" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.ability.type.ApplyContinuousEffect parents = [api_objects["engine.ability.Ability"]] nyan_object = NyanObject("ApplyContinuousEffect", parents) @@ -518,6 +525,111 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.util.activity.Activity + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Activity", parents) + fqon = "engine.util.activity.Activity" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.condition.Condition + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Condition", parents) + fqon = "engine.util.activity.condition.Condition" + 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) + fqon = "engine.util.activity.condition.type.CommandInQueue" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.condition.type.NextCommandIdle + parents = [api_objects["engine.util.activity.condition.Condition"]] + nyan_object = NyanObject("NextCommandIdle", parents) + fqon = "engine.util.activity.condition.type.NextCommandIdle" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.condition.type.NextCommandMove + parents = [api_objects["engine.util.activity.condition.Condition"]] + nyan_object = NyanObject("NextCommandMove", parents) + fqon = "engine.util.activity.condition.type.NextCommandMove" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.event.Event + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Event", parents) + fqon = "engine.util.activity.event.Event" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.event.type.CommandInQueue + parents = [api_objects["engine.util.activity.event.Event"]] + nyan_object = NyanObject("CommandInQueue", parents) + fqon = "engine.util.activity.event.type.CommandInQueue" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.event.type.Wait + parents = [api_objects["engine.util.activity.event.Event"]] + nyan_object = NyanObject("Wait", parents) + fqon = "engine.util.activity.event.type.Wait" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.event.type.WaitAbility + parents = [api_objects["engine.util.activity.event.Event"]] + nyan_object = NyanObject("WaitAbility", parents) + fqon = "engine.util.activity.event.type.WaitAbility" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.Node + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Node", parents) + fqon = "engine.util.activity.node.Node" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.type.Ability + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("Ability", parents) + fqon = "engine.util.activity.node.type.Ability" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.type.End + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("End", parents) + fqon = "engine.util.activity.node.type.End" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.type.Start + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("Start", parents) + fqon = "engine.util.activity.node.type.Start" + 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) + fqon = "engine.util.activity.node.type.XOREventGate" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.type.XORGate + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("XORGate", parents) + fqon = "engine.util.activity.node.type.XORGate" + 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) @@ -2489,6 +2601,13 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("transform_progress", member_type, None, None, 0) api_object.add_member(member) + # engine.ability.type.Activity + api_object = api_objects["engine.ability.type.Activity"] + + member_type = NyanMemberType(api_objects["engine.util.activity.Activity"]) + member = NyanMember("graph", member_type, None, None, 0) + api_object.add_member(member) + # engine.ability.type.ApplyContinuousEffect api_object = api_objects["engine.ability.type.ApplyContinuousEffect"] @@ -3154,6 +3273,64 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("blacklisted_entities", member_type, None, None, 0) api_object.add_member(member) + # engine.util.activity.Activity + api_object = api_objects["engine.util.activity.Activity"] + + member_type = NyanMemberType(api_objects["engine.util.activity.node.type.Start"]) + member = NyanMember("start", member_type, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.condition.Condition + api_object = api_objects["engine.util.activity.condition.Condition"] + + member_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member = NyanMember("next", 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"] + + member = NyanMember("time", N_FLOAT, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.node.type.Ability + api_object = api_objects["engine.util.activity.node.type.Ability"] + + 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.ability.Ability"]) + member_type = NyanMemberType(MemberType.ABSTRACT, (subtype,)) + member = NyanMember("ability", member_type, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.node.type.Start + api_object = api_objects["engine.util.activity.node.type.Start"] + + member_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member = NyanMember("next", 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"] + + key_type = NyanMemberType(api_objects["engine.util.activity.event.Event"]) + 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.activity.node.type.XORGate + api_object = api_objects["engine.util.activity.node.type.XORGate"] + + elem_type = NyanMemberType(api_objects["engine.util.activity.condition.Condition"]) + member_type = NyanMemberType(MemberType.ORDEREDSET, (elem_type,)) + member = NyanMember("next", 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.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 e0d14e703e..1124da8771 100644 --- a/openage/convert/tool/api_export.py +++ b/openage/convert/tool/api_export.py @@ -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. """ Export tool for dumping the nyan API of the engine from the converter. @@ -76,7 +76,7 @@ def create_modpack() -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("engine", "0.4.0", repo="openage") + mod_def.set_info("engine", modpack_version="0.4.1", versionstr="0.4.1", repo="openage") mod_def.add_include("**") diff --git a/openage/nyan/import_tree.py b/openage/nyan/import_tree.py index 8a3880ff8d..5e61feb55c 100644 --- a/openage/nyan/import_tree.py +++ b/openage/nyan/import_tree.py @@ -7,6 +7,8 @@ from enum import Enum import typing +from openage.log import warn + if typing.TYPE_CHECKING: from openage.convert.entity_object.export.formats.nyan_file import NyanFile from openage.nyan.nyan_structs import NyanObject @@ -161,7 +163,9 @@ def add_alias(self, fqon: tuple[str], alias: str) -> None: current_node = current_node.get_child(node_str) except KeyError: # as err: - # TODO: Do not silently fail + # TODO: Fail when the fqon is not found in the tree + warn(f"fqon '{'.'.join(fqon)}' " + "could not be found in import tree") return # raise KeyError(f"fqon '{'.'.join(fqon)}' " # "could not be found in import tree") from err