diff --git a/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt index c1653f29c6..ad42fc342e 100644 --- a/libopenage/CMakeLists.txt +++ b/libopenage/CMakeLists.txt @@ -34,6 +34,7 @@ pxdgen( add_subdirectory("audio") add_subdirectory("console") add_subdirectory("coord") +add_subdirectory("curve") add_subdirectory("cvar") add_subdirectory("datastructure") add_subdirectory("gui") diff --git a/libopenage/coord/CMakeLists.txt b/libopenage/coord/CMakeLists.txt index f6cf992b66..753543b3eb 100644 --- a/libopenage/coord/CMakeLists.txt +++ b/libopenage/coord/CMakeLists.txt @@ -4,9 +4,11 @@ add_sources(libopenage chunk.cpp phys2.cpp phys3.cpp + phys3_serialization.cpp term.cpp tests.cpp tile.cpp + tile_serialization.cpp tile3.cpp vec2.cpp vec2f.cpp diff --git a/libopenage/coord/phys3_serialization.cpp b/libopenage/coord/phys3_serialization.cpp new file mode 100644 index 0000000000..b453a3ed8d --- /dev/null +++ b/libopenage/coord/phys3_serialization.cpp @@ -0,0 +1,30 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#include "phys3_serialization.h" + +namespace openage { +namespace coord { + +std::ostream& operator<<(std::ostream &o, const phys3 &v) { + o << '(' << v.ne << ',' << v.se << ',' << v.up << ')'; + return o; +} + +std::ostream& operator<<(std::ostream &o, const phys3_delta &v) { + o << '(' << v.ne << ',' << v.se << ',' << v.up << ')'; + return o; +} + +std::istream& operator>>(std::istream &i, phys3 &v) { + char c; + i >> c >> v.ne >> c >> v.se >> c >> v.up >> c; + return i; +} + +std::istream& operator>>(std::istream &i, phys3_delta &v) { + char c; + i >> c >> v.ne >> c >> v.se >> c >> v.up >> c; + return i; +} + +}} // openage::coord diff --git a/libopenage/coord/phys3_serialization.h b/libopenage/coord/phys3_serialization.h new file mode 100644 index 0000000000..1412940ab4 --- /dev/null +++ b/libopenage/coord/phys3_serialization.h @@ -0,0 +1,18 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "phys3.h" + +namespace openage { +namespace coord { + +std::ostream& operator<<(std::ostream &o, const phys3 &v); +std::ostream& operator<<(std::ostream &o, const phys3_delta &v); + +std::istream& operator>>(std::istream &i, phys3 &v); +std::istream& operator>>(std::istream &i, phys3_delta &v); + +}} // openage::coord diff --git a/libopenage/coord/tile_serialization.cpp b/libopenage/coord/tile_serialization.cpp new file mode 100644 index 0000000000..552b3d27bb --- /dev/null +++ b/libopenage/coord/tile_serialization.cpp @@ -0,0 +1,30 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#include "tile_serialization.h" + +namespace openage { +namespace coord { + +std::ostream& operator<<(std::ostream &o, const tile &v) { + o << '(' << v.ne << ',' << v.se << ')'; + return o; +} + +std::ostream& operator<<(std::ostream &o, const tile_delta &v) { + o << '(' << v.ne << ',' << v.se << ')'; + return o; +} + +std::istream& operator>>(std::istream &i, tile &v) { + char c; + i >> c >> v.ne >> c >> v.se >> c; + return i; +} + +std::istream& operator>>(std::istream &i, tile_delta &v) { + char c; + i >> c >> v.ne >> c >> v.se >> c; + return i; +} + +}} // openage::coord diff --git a/libopenage/coord/tile_serialization.h b/libopenage/coord/tile_serialization.h new file mode 100644 index 0000000000..2ab6f7c3f5 --- /dev/null +++ b/libopenage/coord/tile_serialization.h @@ -0,0 +1,18 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "tile.h" + +namespace openage { +namespace coord { + +std::ostream& operator<<(std::ostream &o, const tile &v); +std::ostream& operator<<(std::ostream &o, const tile_delta &v); + +std::istream& operator>>(std::istream &i, tile &v); +std::istream& operator>>(std::istream &i, tile_delta &v); + +}} // openage::coord diff --git a/libopenage/curve/CMakeLists.txt b/libopenage/curve/CMakeLists.txt new file mode 100644 index 0000000000..416ef6fe99 --- /dev/null +++ b/libopenage/curve/CMakeLists.txt @@ -0,0 +1,6 @@ +add_sources(libopenage + curve_record_replay.cpp + entities_conductor.cpp + entities_conductor_interpolator.cpp + occurence_curve.cpp +) diff --git a/libopenage/curve/curve_record_replay.cpp b/libopenage/curve/curve_record_replay.cpp new file mode 100644 index 0000000000..49830c597e --- /dev/null +++ b/libopenage/curve/curve_record_replay.cpp @@ -0,0 +1,75 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#include "curve_record_replay.h" + +#include +#include + +#include "../unit/attribute_setter.h" + +namespace openage { +namespace curve { + +CurveRecordReplay::CurveRecordReplay(qtsdl::GuiItemLink *gui_link, bool recorder) + : + stream{std::make_unique(recorder ? std::cerr.rdbuf() : std::cin.rdbuf())}, + current_frame{}, + recorder{recorder}, + gui_link{gui_link} { +} + +CurveRecordReplay::~CurveRecordReplay() { + +} + +std::string CurveRecordReplay::get_file_name() const { + return this->file_name; +} + +void CurveRecordReplay::set_file_name(const std::string &file_name) { + if (this->file_name != file_name) { + this->stream = std::make_unique(file_name, this->recorder ? std::ios_base::out : std::ios_base::in); + this->file_name = file_name; + } +} + +CurveRecord::CurveRecord(qtsdl::GuiItemLink *gui_link) + : + CurveRecordReplay{gui_link, true} { +} + +CurveRecord::~CurveRecord() { +} + +void CurveRecord::perform(UnitContainer &placed_units) { + placed_units.update_all(*this); + ++this->current_frame; +} + +CurveReplay::CurveReplay(qtsdl::GuiItemLink *gui_link) + : + CurveRecordReplay{gui_link, false}, + started{}, + next_available_frame{} { +} + +CurveReplay::~CurveReplay() { +} + +void CurveReplay::perform(UnitContainer &placed_units) { + if (!this->started) { + *this->stream >> this->next_available_frame; + this->started = true; + } + + while (this->next_available_frame <= this->current_frame) { + int id; + *this->stream >> id; + read_attr(*this->stream, id, placed_units); + *this->stream >> this->next_available_frame; + } + + ++this->current_frame; +} + +}} // namespace openage::curve diff --git a/libopenage/curve/curve_record_replay.h b/libopenage/curve/curve_record_replay.h new file mode 100644 index 0000000000..b86c1804e0 --- /dev/null +++ b/libopenage/curve/curve_record_replay.h @@ -0,0 +1,78 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +namespace qtsdl { +class GuiItemLink; +} // qtsdl + +namespace openage { + +class UnitContainer; + +namespace curve { + +class CurveRecord; + +class CurveRecordReplay { +public: + explicit CurveRecordReplay(qtsdl::GuiItemLink *gui_link, bool recorder); + virtual ~CurveRecordReplay(); + + std::string get_file_name() const; + void set_file_name(const std::string &file_name); + + virtual void perform(UnitContainer &placed_units) = 0; + + virtual CurveRecord* get_record() { + return nullptr; + } + +protected: + std::string file_name; + std::unique_ptr stream; + size_t current_frame; + const bool recorder; + +public: + qtsdl::GuiItemLink *gui_link; +}; + +class CurveRecord : public CurveRecordReplay { +public: + explicit CurveRecord(qtsdl::GuiItemLink *gui_link); + virtual ~CurveRecord(); + + virtual void perform(UnitContainer &placed_units) override; + + virtual CurveRecord* get_record() override { + return this; + } + + template + void write_out(id_t id, T value, const char *name) { + *this->stream << this->current_frame << " " << id << " " << name << " " << value << "\n"; + this->stream->flush(); + } + +private: +}; + +class CurveReplay : public CurveRecordReplay { +public: + explicit CurveReplay(qtsdl::GuiItemLink *gui_link); + virtual ~CurveReplay(); + + virtual void perform(UnitContainer &placed_units) override; + +private: + bool started; + size_t next_available_frame; +}; + +}} // namespace openage::curve diff --git a/libopenage/curve/entities_conductor.cpp b/libopenage/curve/entities_conductor.cpp new file mode 100644 index 0000000000..2a0fa1d5f0 --- /dev/null +++ b/libopenage/curve/entities_conductor.cpp @@ -0,0 +1,68 @@ +// Copyright 2015-2016 the openage authors. See copying.md for legal info. + +#include "entities_conductor.h" + +#include + +#include "../error/error.h" + +namespace openage { +namespace curve { + +EntitiesConductor::EntitiesConductor(std::function emerge, std::function vanish) + : + emerge{emerge}, + vanish{vanish} { +} + +void EntitiesConductor::jump(GameClock::moment from, GameClock::moment to) { + // TODO: call appear/vanish for every SPAWN/DIE (depending on the time direction) + // TODO: for each property find closest curve to 'to' that changes it, then apply the last value or a value that is sampled from curve at the 'to' moment +} + +Prediction EntitiesConductor::predict_migration(GameClock::moment current_time, GameClock::moment other_time) const { + const auto prior = std::min(current_time, other_time); + const auto forth = std::max(current_time, other_time); + + const auto cmp_start_cp = [](const OccurenceCurve &c, GameClock::moment p) { return c.origin.time < p; }; + const auto cmp_start_pc = [](GameClock::moment p, const OccurenceCurve &c) { return p < c.origin.time; }; + + const auto cmp_end_ip = [this](size_t index, GameClock::moment p) { return end_time(this->history[index]) < p; }; + const auto cmp_end_pi = [this](GameClock::moment p, size_t index) { return p < end_time(this->history[index]); }; + + ENSURE(this->history.size() == this->history_sorted_by_ends.size(), "index array out of sync"); + + const auto occurences_that_start_inside_begin_it = std::lower_bound(std::begin(this->history), std::end(this->history), prior, cmp_start_cp); + const auto occurences_that_start_inside_end_it = std::upper_bound(std::begin(this->history), std::end(this->history), forth, cmp_start_pc); + + const auto occurencesthat_finish_inside_begin_it = std::lower_bound(std::begin(this->history_sorted_by_ends), std::end(this->history_sorted_by_ends), prior, cmp_end_ip); + const auto occurencesThat_finish_inside_end_it = std::upper_bound(std::begin(this->history_sorted_by_ends), std::end(this->history_sorted_by_ends), forth, cmp_end_pi); + + decltype(this->history_sorted_by_ends) occurences_that_only_finish_inside; + const size_t completely_inside_begin_index = std::distance(std::begin(this->history), occurences_that_start_inside_begin_it); + + std::copy_if(occurencesthat_finish_inside_begin_it, occurencesThat_finish_inside_end_it, std::back_inserter(occurences_that_only_finish_inside), [completely_inside_begin_index](size_t index) { return index < completely_inside_begin_index; }); + std::sort(std::begin(occurences_that_only_finish_inside), std::end(occurences_that_only_finish_inside)); + + const auto occurences_that_start_inside_count = std::distance(occurences_that_start_inside_begin_it, occurences_that_start_inside_end_it); + std::vector trimmed(occurences_that_start_inside_count + occurences_that_only_finish_inside.size()); + + using namespace std::placeholders; + std::transform(occurences_that_start_inside_begin_it, occurences_that_start_inside_end_it, std::begin(trimmed), std::bind(&trim, _1, prior, forth)); + std::transform(std::begin(occurences_that_only_finish_inside), std::end(occurences_that_only_finish_inside), std::begin(trimmed) + occurences_that_start_inside_count, [this, prior, forth](size_t index) { + return trim(this->history[index], prior, forth); + }); + + return trimmed; +} + +void EntitiesConductor::converge(GameClock::moment current_time) { + // TODO: perform silently all rollbacks that unapplied_history causes (simplest: jump() back, fix history, jump() forward) + return; +} + +void EntitiesConductor::append_history(const Prediction &unapplied_history) { + this->unapplied_history.insert(std::end(this->unapplied_history), std::begin(unapplied_history), std::end(unapplied_history)); +} + +}} // namespace openage::curve diff --git a/libopenage/curve/entities_conductor.h b/libopenage/curve/entities_conductor.h new file mode 100644 index 0000000000..f051ea9515 --- /dev/null +++ b/libopenage/curve/entities_conductor.h @@ -0,0 +1,69 @@ +// Copyright 2015-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "../coord/phys2.h" + +#include "game_clock.h" +#include "occurence_curve.h" + +namespace openage { +namespace curve { +/** + * Stores history, gives segments of it, can immediately mutate the world to the given moment. + */ +class EntitiesConductor +{ +public: + /** + * @param emerge function to add a unit + * @param vanish function to remove a unit + */ + EntitiesConductor(std::function emerge, std::function vanish); + + /** + * Immediately mutate the world to the state of specified moment. + * + * @param current_time current time of the world + * @param to desired time + */ + void jump(GameClock::moment current_time, GameClock::moment to); + + /** + * Output what happens between two moments. + * + * @param current_time current time of the world + * @param other_time other point in time + * @return sorted timed occurrences from the past to the future + */ + Prediction predict_migration(GameClock::moment current_time, GameClock::moment other_time) const; + + /** + * Update the history and mutate the world to eliminate the divergence. + * + * @param current_time current time of the world + */ + void converge(GameClock::moment current_time); + + /** + * Queue up some new history. + * + * It needs to be converged to take effect. + * + * @param list of curves + */ + void append_history(const Prediction &unapplied_history); + +private: + std::function emerge; + std::function vanish; + + Prediction history; + std::vector history_sorted_by_ends; + Prediction unapplied_history; +}; + +}} // namespace openage::curve diff --git a/libopenage/curve/entities_conductor_interpolator.cpp b/libopenage/curve/entities_conductor_interpolator.cpp new file mode 100644 index 0000000000..b79e66664d --- /dev/null +++ b/libopenage/curve/entities_conductor_interpolator.cpp @@ -0,0 +1,100 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#include "entities_conductor_interpolator.h" + +#include +#include +#include + +#include "../error/error.h" +#include "../testing/testing.h" + +#include "entities_conductor.h" + +namespace openage { +namespace curve { + +namespace { +GameClock::moment calc_new_frame_border(GameClock::moment desired_moment, GameClock::moment behind, GameClock::moment infront, GameClock::duration min_frame_duration) { + ENSURE(std::max(behind, infront) - std::min(behind, infront) >= min_frame_duration, "behind..infront must be at least min_frame_duration"); + ENSURE((desired_moment < infront && infront < behind) || (behind < infront && infront <= desired_moment), "the behind..infront segment must be oriented towards the desired_moment"); + + const auto frame_count = decltype(min_frame_duration)(desired_moment - infront + decltype(min_frame_duration)(infront > behind ? 1 : 0)) / (1. * min_frame_duration); + const auto int_frame_count = infront > behind ? std::ceil(frame_count) : std::floor(frame_count); + + const auto direction_unit = infront > behind ? 1 : -1; + const auto delta = (decltype(min_frame_duration)::rep(int_frame_count + direction_unit)) * min_frame_duration; + static_assert(std::is_signed::value, "time representation must be a signed number"); + + const auto new_infront = behind + delta > std::decay::type{} ? behind + delta : std::decay::type{}; + + return new_infront; +} +} // namespace + +EntitiesConductorInterpolator::EntitiesConductorInterpolator(EntitiesConductor &conductor, GameClock::duration min_frame_duration) + : + conductor{conductor}, + min_frame_duration{min_frame_duration}, + current_logic_frame_start{}, + current_logic_frame_end{}, + current_time{} { +} + +void EntitiesConductorInterpolator::migrate(GameClock::moment m) { + ENSURE(this->current_logic_frame_start <= this->current_logic_frame_end, "time frame bounds are messed up"); + + if (m >= this->current_logic_frame_end || m < this->current_logic_frame_start) + this->expand_time_segment(m); + + this->play(m); +} + +void EntitiesConductorInterpolator::jump(GameClock::moment m) { + ENSURE(this->current_logic_frame_start <= this->current_logic_frame_end, "time frame bounds are messed up"); + + this->conductor.converge(this->current_time); + + this->conductor.jump(this->current_time, m); + this->predictions_since_current_logic_frame_start.clear(); + this->current_logic_frame_start = this->current_logic_frame_end = m; +} + +void EntitiesConductorInterpolator::expand_time_segment(GameClock::moment m) { + ENSURE(this->current_logic_frame_start <= this->current_logic_frame_end, "time frame bounds are messed up"); + + GameClock::moment *behind = nullptr; + GameClock::moment *infront = nullptr; + + if (m >= this->current_logic_frame_end) + std::tie(behind, infront) = std::make_tuple(&this->current_logic_frame_start, &this->current_logic_frame_end); + else if (m < this->current_logic_frame_start) + std::tie(behind, infront) = std::make_tuple(&this->current_logic_frame_end, &this->current_logic_frame_start); + else + ENSURE(false, "the moment is not outside of the current frame, so no frame moving needed"); + + std::tie(*behind, *infront) = std::make_pair(this->current_time, calc_new_frame_border(m, *behind, *infront, this->min_frame_duration)); + + this->conductor.converge(*behind); + this->predictions_since_current_logic_frame_start = this->conductor.predict_migration(*behind, *infront); +} + +void EntitiesConductorInterpolator::play(GameClock::moment m) { +} + +namespace tests { +void advance_frame() { + calc_new_frame_border(GameClock::moment{5}, GameClock::moment{0}, GameClock::moment{5}, GameClock::duration{5}) == GameClock::moment{10} || TESTFAIL; + calc_new_frame_border(GameClock::moment{10}, GameClock::moment{0}, GameClock::moment{5}, GameClock::duration{5}) == GameClock::moment{15} || TESTFAIL; + calc_new_frame_border(GameClock::moment{35}, GameClock::moment{10}, GameClock::moment{30}, GameClock::duration{20}) == GameClock::moment{50} || TESTFAIL; + calc_new_frame_border(GameClock::moment{58}, GameClock::moment{10}, GameClock::moment{30}, GameClock::duration{20}) == GameClock::moment{70} || TESTFAIL; + calc_new_frame_border(GameClock::moment{70}, GameClock::moment{10}, GameClock::moment{30}, GameClock::duration{20}) == GameClock::moment{90} || TESTFAIL; + + calc_new_frame_border(GameClock::moment{29}, GameClock::moment{40}, GameClock::moment{30}, GameClock::duration{10}) == GameClock::moment{20} || TESTFAIL; + calc_new_frame_border(GameClock::moment{20}, GameClock::moment{40}, GameClock::moment{30}, GameClock::duration{10}) == GameClock::moment{20} || TESTFAIL; + calc_new_frame_border(GameClock::moment{5}, GameClock::moment{40}, GameClock::moment{30}, GameClock::duration{10}) == GameClock::moment{0} || TESTFAIL; +} + +} // tests + +}} // namespace openage::curve diff --git a/libopenage/curve/entities_conductor_interpolator.h b/libopenage/curve/entities_conductor_interpolator.h new file mode 100644 index 0000000000..683ea37f82 --- /dev/null +++ b/libopenage/curve/entities_conductor_interpolator.h @@ -0,0 +1,71 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "game_clock.h" +#include "occurence_curve.h" + +namespace openage { +namespace curve { + +class EntitiesConductor; + +/** + * Obtains segments of history and interpolates them. + * + * Holds the current game time. + */ +class EntitiesConductorInterpolator +{ +public: + /** + * @param conductor holds the history of the world and can jump between moments in time + * @param min_frame_duration minimal length of a time segment to hold + */ + explicit EntitiesConductorInterpolator(EntitiesConductor &conductor, GameClock::duration min_frame_duration); + + /** + * Move the world to the specific moment by performing everything between current time and that moment. + * + * Does converge when loading different segment. + * + * @param m target moment + */ + void migrate(GameClock::moment m); + + /** + * Immediately mutate the world to the state of specified moment. + * + * Does converge before jumping. + * + * @param m target moment + */ + void jump(GameClock::moment m); + +private: + /** + * @param m target moment (must be outside of the current time segment) + */ + void expand_time_segment(GameClock::moment m); + + void play(GameClock::moment m); + + EntitiesConductor &conductor; + + GameClock::duration min_frame_duration; + + /** + * 'start' is included into the segment, but the 'end' is not. + * 'Start' <= 'end'. + */ + GameClock::moment current_logic_frame_start; + GameClock::moment current_logic_frame_end; + + GameClock::moment current_time; + + Prediction predictions_since_current_logic_frame_start; +}; + +}} // namespace openage::curve diff --git a/libopenage/curve/game_clock.h b/libopenage/curve/game_clock.h new file mode 100644 index 0000000000..cc3777e80e --- /dev/null +++ b/libopenage/curve/game_clock.h @@ -0,0 +1,22 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +namespace openage { +namespace curve { + +struct GameClock { + using moment = std::chrono::milliseconds; + static_assert(std::is_trivially_default_constructible::value, "instance should be trivially-default-constructible since we use it everywhere"); + + using duration = std::chrono::milliseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static const bool is_steady = false; +}; + +}} // namespace openage::curve diff --git a/libopenage/curve/occurence.h b/libopenage/curve/occurence.h new file mode 100644 index 0000000000..950b6fbd86 --- /dev/null +++ b/libopenage/curve/occurence.h @@ -0,0 +1,25 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +namespace openage { +namespace curve { + +class Occurence +{ +public: + enum class kind + { + ATTACK, + BUILD, + DIE, + GARRISON, + GATHER, + HEAL, + MOVE, + PRODUCE, + SPAWN, + }; +}; + +}} // namespace openage::curve diff --git a/libopenage/curve/occurence_curve.cpp b/libopenage/curve/occurence_curve.cpp new file mode 100644 index 0000000000..7eb854f3a8 --- /dev/null +++ b/libopenage/curve/occurence_curve.cpp @@ -0,0 +1,110 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#include "occurence_curve.h" + +#include +#include +#include +#include + +#include "../error/error.h" + +namespace openage { +namespace curve { + +namespace +{ +template +bool has(T u, ArgTypes ...args) { + const std::array v{args...}; + return std::find(std::begin(v), std::end(v), u) != std::end(v); +} + +template +bool is(const OccurenceCurve& c); + +template<> +bool is(const OccurenceCurve& c) { + return has( + c.meta.kind, + Occurence::kind::ATTACK, + Occurence::kind::BUILD, + Occurence::kind::DIE, + Occurence::kind::GATHER, + Occurence::kind::GARRISON, + Occurence::kind::PRODUCE, + Occurence::kind::SPAWN + ); +} + +template<> +bool is(const OccurenceCurve& c) { + return has( + c.meta.kind, + Occurence::kind::HEAL, + Occurence::kind::MOVE + ); +} + +void assert_failure(const OccurenceCurve& c, const char *message) { + const auto kindVal = "'" + std::to_string(static_cast>(c.meta.kind)) + "'"; + ENSURE(false, message + kindVal); +} + +template +Values trim(const Values& v, GameClock::moment begin_time, GameClock::moment end_time, GameClock::moment prior, GameClock::moment forth) { + const T new_initial = begin_time < prior ? v.initial + (v.final - v.initial) / ((end_time - begin_time) / (prior - begin_time)) : v.initial; + const T new_final = end_time < forth ? v.initial + (v.final - v.initial) / ((end_time - begin_time) / (forth - begin_time)) : v.final; + return {new_initial, new_final}; +} + +} // namespace + +GameClock::moment end_time(const OccurenceCurve& c) { + if (is(c)) { + return c.origin.time; + } else if (is(c)) { + return c.desc.segment.time_instances.end_time; + } else { + assert_failure(c, "can not deduce the end time of the curve because of its unknown kind: "); + return c.origin.time; + } +} + +OccurenceCurve trim(const OccurenceCurve& c, GameClock::moment prior, GameClock::moment forth) { + ENSURE(prior < forth, "non-normalized time segment"); + + const auto end = end_time(c); + ENSURE(prior <= c.origin.time && end < forth, "curve does not intersect the time segment"); + + auto result = c; + + if (is(c)) { + /** + * The curve intersects the time segment and curve is a point, therefore it's inside it. + */ + } else if (is(c)) { + result.origin.time = std::min(c.origin.time, prior); + result.desc.segment.time_instances.end_time = std::max(c.desc.segment.time_instances.end_time, forth); + + switch (c.meta.kind) { + case Occurence::kind::HEAL: + result.desc.segment.value.amount = trim(c.desc.segment.value.amount, c.origin.time, end, prior, forth); + break; + + case Occurence::kind::MOVE: + result.desc.segment.value.pos = trim(c.desc.segment.value.pos, c.origin.time, end, prior, forth); + break; + + default: + assert_failure(c, "can not trim segment-curve because of its wrong kind: "); + break; + } + } else { + assert_failure(c, "can not trim curve because of its unknown kind: "); + } + + return result; +} + +}} // namespace openage::curve diff --git a/libopenage/curve/occurence_curve.h b/libopenage/curve/occurence_curve.h new file mode 100644 index 0000000000..2db95cf39f --- /dev/null +++ b/libopenage/curve/occurence_curve.h @@ -0,0 +1,112 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "../coord/phys2.h" + +#include "game_clock.h" +#include "occurence.h" + +namespace openage { +namespace curve { + +/** + * TODO: establish int sizes + */ +struct OccurenceCurveMetadata { + /** + * id of the concerned unit (optional) + */ + uint32_t unit_id; + + /** + * id of an additional unit (optional) + */ + uint32_t aux_unit_id; + + /** + * type like 'move', 'attack', etc. + */ + Occurence::kind kind; +}; + +enum class Shape { + POINT, + SEGMENT, +}; + +struct Origin { + GameClock::moment time; +}; + +template +struct TimeInstances; + +template<> +struct TimeInstances { +}; + +template<> +struct TimeInstances { + GameClock::moment end_time; +}; + +template::type> +struct Values { +}; + +template +struct Values::value>::type> { + T val; +}; + + +template +struct Values::value>::type> { + T initial; + T final; +}; + +/** + * TODO: replace with a std::variant + */ +template +struct ControlPoints { + TimeInstances time_instances; + union Value { + Values empty; + Values amount; + Values pos; + } value; +}; + +/** + * TODO: replace with a std::variant + * TODO: establish int sizes + */ +struct OccurenceCurve { + OccurenceCurveMetadata meta; + + Origin origin; + + union { + ControlPoints point; + ControlPoints segment; + } desc; +}; + +using Prediction = std::vector; + +GameClock::moment end_time(const OccurenceCurve& c); + +/** + * The curve must intersect the time segment. + */ +OccurenceCurve trim(const OccurenceCurve& c, GameClock::moment prior, GameClock::moment forth); + +}} // namespace openage::curve diff --git a/libopenage/game_control.cpp b/libopenage/game_control.cpp index 7287f4d301..fac8bed2db 100644 --- a/libopenage/game_control.cpp +++ b/libopenage/game_control.cpp @@ -4,6 +4,7 @@ #include "util/strings.h" #include "terrain/terrain_chunk.h" #include "game_control.h" +#include "unit/attribute_watcher.h" namespace openage { @@ -19,6 +20,7 @@ void OutputMode::announce() { void OutputMode::set_game_control(GameControl *game_control) { this->game_control = game_control; + this->announce(); } CreateMode::CreateMode(qtsdl::GuiItemLink *gui_link) @@ -111,7 +113,8 @@ ActionMode::ActionMode(qtsdl::GuiItemLink *gui_link) UnitType &type = *player->get_type(590); // TODO tile position - engine.get_game()->placed_units.new_unit(type, *player, this->mousepos_phys3); + if (curve::CurveRecord *record = engine.get_game()->get_record()) + engine.get_game()->placed_units.new_unit(*record, type, *player, this->mousepos_phys3); } }); this->bind(action.get("KILL_UNIT"), [this](const input::action_arg_t &) { @@ -331,16 +334,19 @@ bool ActionMode::place_selection(coord::phys3 point) { // first create foundation using the producer Engine &engine = Engine::get(); UnitContainer *container = &engine.get_game()->placed_units; - UnitReference new_building = container->new_unit(*this->type_focus, *this->game_control->get_current_player(), point); - - // task all selected villagers to build - // TODO: editor placed objects are completed already - if (new_building.is_valid()) { - Command cmd(*this->game_control->get_current_player(), new_building.get()); - cmd.set_ability(ability_type::build); - cmd.add_flag(command_flag::direct); - this->selection->all_invoke(cmd); - return true; + + if (curve::CurveRecord *record = engine.get_game()->get_record()) { + UnitReference new_building = container->new_unit(*record, *this->type_focus, *this->game_control->get_current_player(), point); + + // task all selected villagers to build + // TODO: editor placed objects are completed already + if (new_building.is_valid()) { + Command cmd(*this->game_control->get_current_player(), new_building.get()); + cmd.set_ability(ability_type::build); + cmd.add_flag(command_flag::direct); + this->selection->all_invoke(cmd); + return true; + } } } return false; @@ -380,12 +386,13 @@ void ActionMode::announce() { } void ActionMode::announce_resources() { - if (Player *player = this->game_control->get_current_player()) { - for (auto i = static_cast::type>(game_resource::RESOURCE_TYPE_COUNT); i != 0; --i) { - auto resource_type = static_cast(i - 1); - emit this->gui_signals.resource_changed(resource_type, static_cast(player->amount(resource_type))); + if (this->game_control) + if (Player *player = this->game_control->get_current_player()) { + for (auto i = static_cast::type>(game_resource::RESOURCE_TYPE_COUNT); i != 0; --i) { + auto resource_type = static_cast(i - 1); + emit this->gui_signals.resource_changed(resource_type, static_cast(player->amount(resource_type))); + } } - } } void ActionMode::announce_buttons_type() { @@ -524,7 +531,9 @@ void EditorMode::paint_entity_at(const coord::window &point, const bool del) { // tile is empty so try creating a unit UnitContainer *container = &engine.get_game()->placed_units; - container->new_unit(*selected_type, *this->game_control->get_current_player(), mousepos_phys3); + + if (curve::CurveRecord *record = engine.get_game()->get_record()) + container->new_unit(*record, *selected_type, *this->game_control->get_current_player(), mousepos_phys3); } } @@ -659,10 +668,10 @@ void GameControl::set_modes(const std::vector &modes) { for (auto mode : this->modes) mode->set_game_control(this); - if (old_mode_index != -1 && old_mode_index < std::distance(std::begin(this->modes), std::end(this->modes))) - this->set_mode(old_mode_index); - emit this->gui_signals.modes_changed(this->active_mode, this->active_mode_index); + + if (old_mode_index != -1 && old_mode_index < std::distance(std::begin(this->modes), std::end(this->modes))) + this->set_mode(old_mode_index, true); } void GameControl::announce_mode() { @@ -692,17 +701,22 @@ Player* GameControl::get_current_player() const { return nullptr; } -void GameControl::set_mode(int mode_index) { +void GameControl::set_mode(int mode_index, bool signal_if_unchanged) { + bool need_to_signal = signal_if_unchanged; + if (mode_index != -1) { if (mode_index < std::distance(std::begin(this->modes), std::end(this->modes)) && this->modes[mode_index]->available() && this->active_mode_index != mode_index) { - engine->get_input_manager().remove_context(this->active_mode); - - // exit from the old mode + ENSURE(!this->active_mode == (this->active_mode_index == -1), "inconsistency between the active mode index and pointer"); if (this->active_mode) { - this->active_mode->on_exit(); + engine->get_input_manager().remove_context(this->active_mode); + + // exit from the old mode + if (this->active_mode) { + this->active_mode->on_exit(); + } } // set the new active mode @@ -713,7 +727,7 @@ void GameControl::set_mode(int mode_index) { // update the context engine->get_input_manager().register_context(this->active_mode); - emit this->gui_signals.mode_changed(this->active_mode, this->active_mode_index); + need_to_signal = true; } } else { if (this->active_mode) { @@ -722,9 +736,12 @@ void GameControl::set_mode(int mode_index) { this->active_mode_index = -1; this->active_mode = nullptr; - emit this->gui_signals.mode_changed(this->active_mode, this->active_mode_index); + need_to_signal = true; } } + + if (need_to_signal) + emit this->gui_signals.mode_changed(this->active_mode, this->active_mode_index); } } // openage diff --git a/libopenage/game_control.h b/libopenage/game_control.h index 7137a1c82b..bf1706277d 100644 --- a/libopenage/game_control.h +++ b/libopenage/game_control.h @@ -291,7 +291,7 @@ class GameControl : void set_modes(const std::vector &modes); - void set_mode(int mode); + void set_mode(int mode, bool signal_if_unchanged=false); void announce_mode(); void announce_current_player_name(); diff --git a/libopenage/gamestate/game_main.cpp b/libopenage/gamestate/game_main.cpp index 14b2a119d9..8016d8ddda 100644 --- a/libopenage/gamestate/game_main.cpp +++ b/libopenage/gamestate/game_main.cpp @@ -4,6 +4,9 @@ #include "../terrain/terrain.h" #include "../unit/unit_type.h" #include "game_main.h" + +#include "../curve/curve_record_replay.h" +#include "../unit/attribute_watcher.h" #include "game_spec.h" #include "generator.h" @@ -13,7 +16,8 @@ GameMain::GameMain(const Generator &generator) : OptionNode{"GameMain"}, terrain{generator.terrain()}, - spec{generator.get_spec()} { + spec{generator.get_spec()}, + record_replay{} { // players unsigned int i = 0; @@ -29,7 +33,9 @@ GameMain::GameMain(const Generator &generator) // initialise units this->placed_units.set_terrain(this->terrain); - generator.add_units(*this); + + curve::CurveRecord watcher{nullptr}; + generator.add_units(*this, watcher); } GameMain::~GameMain() { @@ -49,7 +55,25 @@ GameSpec *GameMain::get_spec() { } void GameMain::update() { - this->placed_units.update_all(); + if (this->record_replay) { + this->record_replay->perform(this->placed_units); + } else { + // TODO: store pointer to the printer as a member, make bindable + curve::CurveRecord watcher{nullptr}; + this->placed_units.update_all(watcher); + } +} + +curve::CurveRecordReplay* GameMain::get_record_replay() const { + return this->record_replay; +} + +curve::CurveRecord* GameMain::get_record() const { + return this->record_replay ? this->record_replay->get_record() : nullptr; +} + +void GameMain::set_record_replay(curve::CurveRecordReplay *record_replay) { + this->record_replay = record_replay; } Civilisation *GameMain::add_civ(int civ_id) { @@ -71,6 +95,15 @@ void GameMainHandle::set_engine(Engine *engine) { this->engine = engine; } +curve::CurveRecordReplay* GameMainHandle::get_record_replay() const { + return this->game ? this->game->get_record_replay() : nullptr; +} + +void GameMainHandle::set_record_replay(curve::CurveRecordReplay *record_replay) { + if (this->game) + this->game->set_record_replay(record_replay); +} + void GameMainHandle::clear() { if (this->engine) { this->game = nullptr; diff --git a/libopenage/gamestate/game_main.h b/libopenage/gamestate/game_main.h index e4a55346be..d5854984d3 100644 --- a/libopenage/gamestate/game_main.h +++ b/libopenage/gamestate/game_main.h @@ -17,6 +17,10 @@ namespace openage { class Generator; class Terrain; +namespace curve { +class CurveRecordReplay; +} // namespace curve + /** * Contains information for a single game * This information must be synced across network clients @@ -49,6 +53,11 @@ class GameMain : public options::OptionNode { */ void update(); + curve::CurveRecordReplay* get_record_replay() const; + void set_record_replay(curve::CurveRecordReplay *record_replay); + + curve::CurveRecord* get_record() const; + /** * map information */ @@ -78,6 +87,8 @@ class GameMain : public options::OptionNode { std::vector> civs; std::shared_ptr spec; + + curve::CurveRecordReplay *record_replay; }; } // openage @@ -102,6 +113,9 @@ class GameMainHandle { void set_engine(Engine *engine); + curve::CurveRecordReplay* get_record_replay() const; + void set_record_replay(curve::CurveRecordReplay *record_replay); + void clear(); void set_game(std::unique_ptr game); diff --git a/libopenage/gamestate/game_save.cpp b/libopenage/gamestate/game_save.cpp index 976c998a5d..8284d4a013 100644 --- a/libopenage/gamestate/game_save.cpp +++ b/libopenage/gamestate/game_save.cpp @@ -10,6 +10,7 @@ #include "../unit/producer.h" #include "../unit/unit.h" #include "../unit/unit_type.h" +#include "../unit/attribute_watcher.h" #include "game_main.h" #include "game_save.h" #include "game_spec.h" @@ -30,7 +31,7 @@ void save_unit(std::ofstream &file, Unit *unit) { } } -void load_unit(std::ifstream &file, openage::GameMain *game) { +void load_unit(std::ifstream &file, openage::GameMain *game, curve::CurveRecord &watcher) { int pr_id; int player_no; coord::phys_t ne, se; @@ -40,7 +41,7 @@ void load_unit(std::ifstream &file, openage::GameMain *game) { file >> se; UnitType &saved_type = *game->get_player(player_no)->get_type(pr_id); - auto ref = game->placed_units.new_unit(saved_type, game->players[player_no], coord::tile{ne, se}.to_phys2().to_phys3()); + auto ref = game->placed_units.new_unit(watcher, saved_type, game->players[player_no], coord::tile{ne, se}.to_phys2().to_phys3()); bool has_building_attr; file >> has_building_attr; @@ -48,7 +49,7 @@ void load_unit(std::ifstream &file, openage::GameMain *game) { float completed; file >> completed; if (completed >= 1.0f && ref.is_valid()) { - complete_building(*ref.get()); + complete_building(watcher, *ref.get()); } } } @@ -140,8 +141,10 @@ void load(openage::GameMain *game, std::string fname) { game->placed_units.reset(); unsigned int num_units; file >> num_units; + + curve::CurveRecord watcher{nullptr}; for (unsigned int u = 0; u < num_units; ++u) { - load_unit( file, game ); + load_unit(file, game, watcher); } } diff --git a/libopenage/gamestate/generator.cpp b/libopenage/gamestate/generator.cpp index f42c199047..b2c09ff487 100644 --- a/libopenage/gamestate/generator.cpp +++ b/libopenage/gamestate/generator.cpp @@ -242,7 +242,7 @@ std::shared_ptr Generator::terrain() const { return terrain; } -void Generator::add_units(GameMain &m) const { +void Generator::add_units(GameMain &m, curve::CurveRecord &watcher) const { for (auto &r : this->regions) { // Regions filled with resource objects @@ -254,7 +254,7 @@ void Generator::add_units(GameMain &m) const { break; } for (auto &tile : r.get_tiles()) { - m.placed_units.new_unit(*otype, p, tile.to_tile3().to_phys3()); + m.placed_units.new_unit(watcher, *otype, p, tile.to_tile3().to_phys3()); } } @@ -273,18 +273,18 @@ void Generator::add_units(GameMain &m) const { tile.se -= 1; // Place a completed town center - auto ref = m.placed_units.new_unit(*tctype, p, tile.to_tile3().to_phys3()); + auto ref = m.placed_units.new_unit(watcher, *tctype, p, tile.to_tile3().to_phys3()); if (ref.is_valid()) { - complete_building(*ref.get()); + complete_building(watcher, *ref.get()); } // Place three villagers tile.ne -= 1; - m.placed_units.new_unit(*fvtype, p, tile.to_tile3().to_phys3()); + m.placed_units.new_unit(watcher, *fvtype, p, tile.to_tile3().to_phys3()); tile.se += 1; - m.placed_units.new_unit(*mvtype, p, tile.to_tile3().to_phys3()); + m.placed_units.new_unit(watcher, *mvtype, p, tile.to_tile3().to_phys3()); tile.se += 1; - m.placed_units.new_unit(*fvtype, p, tile.to_tile3().to_phys3()); + m.placed_units.new_unit(watcher, *fvtype, p, tile.to_tile3().to_phys3()); } } } diff --git a/libopenage/gamestate/generator.h b/libopenage/gamestate/generator.h index 0c3dae9ba1..202839833f 100644 --- a/libopenage/gamestate/generator.h +++ b/libopenage/gamestate/generator.h @@ -20,6 +20,10 @@ class Terrain; class GameMain; class Engine; +namespace curve { +class CurveRecord; +} // namespace curve + namespace rng { class RNG; } // openage::rng @@ -156,7 +160,7 @@ class Generator : public qtsdl::GuiPropertyMap { /** * places all initial objects */ - void add_units(GameMain &m) const; + void add_units(GameMain &m, curve::CurveRecord &watcher) const; std::unique_ptr create(std::shared_ptr spec); diff --git a/libopenage/gui/CMakeLists.txt b/libopenage/gui/CMakeLists.txt index 4a7a1f8354..502cd35174 100644 --- a/libopenage/gui/CMakeLists.txt +++ b/libopenage/gui/CMakeLists.txt @@ -2,6 +2,7 @@ add_sources(libopenage assetmanager_link.cpp actions_list_model.cpp category_contents_list_model.cpp + curve_record_replay_link.cpp engine_link.cpp game_control_link.cpp game_creator.cpp diff --git a/libopenage/gui/curve_record_replay_link.cpp b/libopenage/gui/curve_record_replay_link.cpp new file mode 100644 index 0000000000..9ce106bc41 --- /dev/null +++ b/libopenage/gui/curve_record_replay_link.cpp @@ -0,0 +1,50 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#include "curve_record_replay_link.h" + +#include + +namespace openage { +namespace gui { + +namespace { +const int registration = qmlRegisterUncreatableType("yay.sfttech.openage", 1, 0, "CurveRecordReplay", "CurveRecordReplay is an abstract uncreatable base"); +const int registration_recorder = qmlRegisterType("yay.sfttech.openage", 1, 0, "CurveRecord"); +const int registration_player = qmlRegisterType("yay.sfttech.openage", 1, 0, "CurveReplay"); +} + +CurveRecordReplayLink::CurveRecordReplayLink(QObject *parent) + : + GuiItemQObject{parent}, + GuiItemInterface{} { + Q_UNUSED(registration); +} + +QString CurveRecordReplayLink::get_file_name() const { + return this->file_name; +} + +void CurveRecordReplayLink::set_file_name(const QString &file_name) { + static auto f = [] (curve::CurveRecordReplay *_this, const QString &file_name) { + _this->set_file_name(file_name.toStdString()); + }; + this->s(f, this->file_name, file_name); +} + +void CurveRecordReplayLink::on_core_adopted() { + this->file_name = QString::fromStdString(unwrap(this)->get_file_name()); +} + +CurveRecordLink::CurveRecordLink(QObject *parent) + : + Inherits{parent} { + Q_UNUSED(registration_recorder); +} + +CurveReplayLink::CurveReplayLink(QObject *parent) + : + Inherits{parent} { + Q_UNUSED(registration_player); +} + +}} // namespace openage::gui diff --git a/libopenage/gui/curve_record_replay_link.h b/libopenage/gui/curve_record_replay_link.h new file mode 100644 index 0000000000..56a57f9c57 --- /dev/null +++ b/libopenage/gui/curve_record_replay_link.h @@ -0,0 +1,82 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include "guisys/link/gui_item.h" + +#include "../curve/curve_record_replay.h" + +namespace openage { +namespace gui { +class CurveRecordReplayLink; +class CurveRecordLink; +class CurveReplayLink; +}} // namespace openage::gui + +namespace qtsdl { +template<> +struct Wrap { + using Type = openage::gui::CurveRecordReplayLink; +}; + +template<> +struct Unwrap { + using Type = openage::curve::CurveRecordReplay; +}; + +template<> +struct Wrap { + using Type = openage::gui::CurveRecordLink; +}; + +template<> +struct Unwrap { + using Type = openage::curve::CurveRecord; +}; + +template<> +struct Wrap { + using Type = openage::gui::CurveReplayLink; +}; + +template<> +struct Unwrap { + using Type = openage::curve::CurveReplay; +}; + +} // namespace qtsdl + +namespace openage { +namespace gui { + +class CurveRecordReplayLink : public qtsdl::GuiItemQObject, public qtsdl::GuiItemInterface { + Q_OBJECT + + Q_PROPERTY(QString fileName READ get_file_name WRITE set_file_name) + +public: + explicit CurveRecordReplayLink(QObject *parent=nullptr); + + QString get_file_name() const; + void set_file_name(const QString &file_name); + +private: + virtual void on_core_adopted() override; + +protected: + QString file_name; +}; + +class CurveRecordLink : public qtsdl::Inherits { + Q_OBJECT +public: + explicit CurveRecordLink(QObject *parent=nullptr); +}; + +class CurveReplayLink : public qtsdl::Inherits { + Q_OBJECT +public: + explicit CurveReplayLink(QObject *parent=nullptr); +}; + +}} // namespace openage::gui diff --git a/libopenage/gui/game_control_link.cpp b/libopenage/gui/game_control_link.cpp index 5e64e67cc1..955ed31d4a 100644 --- a/libopenage/gui/game_control_link.cpp +++ b/libopenage/gui/game_control_link.cpp @@ -198,7 +198,7 @@ GameControlLink::GameControlLink(QObject *parent) GuiItem{this}, mode{}, effective_mode_index{-1}, - mode_index{}, + mode_index{-1}, engine{}, game{}, current_civ_index{} { @@ -241,7 +241,7 @@ int GameControlLink::get_mode_index() const { void GameControlLink::set_mode_index(int mode) { static auto f = [] (GameControl *_this, int mode) { - _this->set_mode(mode); + _this->set_mode(mode, true); }; this->sf(f, this->mode_index, mode); @@ -312,6 +312,7 @@ void GameControlLink::on_modes_changed(OutputMode *mode, int mode_index) { this->i(f, this->mode_index); this->on_mode_changed(mode, mode_index); + emit this->modes_changed(); } void GameControlLink::on_current_player_name_changed(const std::string ¤t_player_name) { diff --git a/libopenage/gui/game_control_link.h b/libopenage/gui/game_control_link.h index ecae629bf2..a01bc4b1ba 100644 --- a/libopenage/gui/game_control_link.h +++ b/libopenage/gui/game_control_link.h @@ -231,7 +231,7 @@ class GameControlLink : public qtsdl::GuiItemQObject, public QQmlParserStatus, p Q_PROPERTY(openage::gui::OutputModeLink* mode READ get_mode NOTIFY mode_changed) Q_PROPERTY(int effectiveModeIndex READ get_effective_mode_index NOTIFY mode_changed) Q_PROPERTY(int modeIndex READ get_mode_index WRITE set_mode_index) - Q_PROPERTY(QVariantList modes READ get_modes WRITE set_modes) + Q_PROPERTY(QVariantList modes READ get_modes WRITE set_modes NOTIFY modes_changed) Q_PROPERTY(openage::gui::EngineLink* engine READ get_engine WRITE set_engine) Q_PROPERTY(openage::gui::GameMainLink* game READ get_game WRITE set_game) Q_PROPERTY(QString currentPlayerName READ get_current_player_name NOTIFY current_player_name_changed) @@ -261,6 +261,7 @@ class GameControlLink : public qtsdl::GuiItemQObject, public QQmlParserStatus, p signals: void mode_changed(); + void modes_changed(); void current_player_name_changed(); void current_civ_index_changed(); diff --git a/libopenage/gui/game_main_link.cpp b/libopenage/gui/game_main_link.cpp index 603d9c497d..ee9e92ff38 100644 --- a/libopenage/gui/game_main_link.cpp +++ b/libopenage/gui/game_main_link.cpp @@ -6,6 +6,7 @@ #include "../engine.h" #include "engine_link.h" +#include "curve_record_replay_link.h" namespace openage { namespace gui { @@ -57,6 +58,18 @@ void GameMainLink::set_engine(EngineLink *engine) { this->s(f, this->engine, engine); } +CurveRecordReplayLink* GameMainLink::get_record_replay() const { + return this->record_replay; +} + +void GameMainLink::set_record_replay(CurveRecordReplayLink *record_replay) { + static auto f = [] (GameMainHandle *_this, curve::CurveRecordReplay *record_replay) { + if (auto game = _this->get_game()) + game->set_record_replay(record_replay); + }; + this->s(f, this->record_replay, record_replay); +} + void GameMainLink::clear() { static auto f = [] (GameMainHandle *_this) { _this->clear(); diff --git a/libopenage/gui/game_main_link.h b/libopenage/gui/game_main_link.h index 9836dc2b61..e8d0d54374 100644 --- a/libopenage/gui/game_main_link.h +++ b/libopenage/gui/game_main_link.h @@ -13,6 +13,7 @@ namespace gui { class EngineLink; class GameMainLink; +class CurveRecordReplayLink; }} // namespace openage::gui @@ -39,6 +40,7 @@ class GameMainLink : public qtsdl::GuiItemQObject, public QQmlParserStatus, publ Q_ENUMS(State) Q_PROPERTY(State state READ get_state NOTIFY state_changed) Q_PROPERTY(openage::gui::EngineLink* engine READ get_engine WRITE set_engine) + Q_PROPERTY(openage::gui::CurveRecordReplayLink* recordReplay READ get_record_replay WRITE set_record_replay) public: explicit GameMainLink(QObject *parent=nullptr); @@ -53,6 +55,9 @@ class GameMainLink : public qtsdl::GuiItemQObject, public QQmlParserStatus, publ EngineLink* get_engine() const; void set_engine(EngineLink *engine); + CurveRecordReplayLink* get_record_replay() const; + void set_record_replay(CurveRecordReplayLink *record_replay); + Q_INVOKABLE void clear(); signals: @@ -69,6 +74,7 @@ private slots: State state; bool active; EngineLink *engine; + CurveRecordReplayLink *record_replay; }; }} // namespace openage::gui diff --git a/libopenage/gui/qml/CreateGameWhenReady.qml b/libopenage/gui/qml/CreateGameWhenReady.qml new file mode 100644 index 0000000000..c7c1f3b089 --- /dev/null +++ b/libopenage/gui/qml/CreateGameWhenReady.qml @@ -0,0 +1,28 @@ +// Copyright 2015-2016 the openage authors. See copying.md for legal info. + +import QtQuick 2.4 +import yay.sfttech.openage 1.0 + +GameCreator { + id: root + + property bool enabled: false + property GameControl gameControl + property int gameControlTargetModeIndex + + property int specState: gameSpec ? gameSpec.state : GameSpec.Null + property int gameState: game ? game.state : GameMain.Null + + onSpecStateChanged: { + if (enabled && specState == GameSpec.Ready) + activate() + } + + onGameStateChanged: { + if (enabled && gameState == GameMain.Running) + if (gameControl.modeIndex != -1) + gameControl.modeIndex = gameControlTargetModeIndex + else + console.error("CreateGameWhenReady: could not find the desired mode to switch to") + } +} diff --git a/libopenage/gui/qml/main.qml b/libopenage/gui/qml/main.qml index 2cc9b45cad..4a927d289e 100644 --- a/libopenage/gui/qml/main.qml +++ b/libopenage/gui/qml/main.qml @@ -50,22 +50,49 @@ Item { GameMain { id: gameObj + property bool recording: true + /** * States: Null, Running */ + onStateChanged: + if (state == GameMain.Running) + recordReplay = recording ? recordObj : replayObj engine: Engine LR.tag: "game" } + CurveRecord { + id: recordObj + + fileName: gameObj.recording ? "replay.txt" : "" + + LR.tag: "record" + } + + CurveReplay { + id: replayObj + + fileName: gameObj.recording ? "" : "replay.txt" + + LR.tag: "replay" + } + GameControl { id: gameControlObj engine: Engine game: gameObj - modes: [createModeObj, editorModeObj, actionModeObj] + /** + * must be run after the engine is attached + */ + Component.onCompleted: { + modes = [createModeObj, editorModeObj, actionModeObj] + modeIndex = 0 + } LR.tag: "gamecontrol" } @@ -118,6 +145,17 @@ Item { LR.tag: "editorMode" } + CreateGameWhenReady { + enabled: createWhenReady.checked + + game: gameObj + gameSpec: specObj + generatorParameters: genParamsObj + gameControl: gameControlObj + + gameControlTargetModeIndex: gameControlObj.modes.indexOf(actionModeObj) + } + states: [ State { id: creationMode @@ -134,6 +172,11 @@ Item { generatorParameters: genParamsObj gameSpec: specObj game: gameObj + }, + CheckBoxFlat { + id: createWhenReady + text: "create_when_ready" + visible: specObj.state == GameSpec.Loading } ] diff --git a/libopenage/terrain/CMakeLists.txt b/libopenage/terrain/CMakeLists.txt index 2dbe9a39cc..574be71a51 100644 --- a/libopenage/terrain/CMakeLists.txt +++ b/libopenage/terrain/CMakeLists.txt @@ -4,4 +4,5 @@ add_sources(libopenage terrain_object.cpp terrain_outline.cpp terrain_search.cpp + tile_range_serialization.cpp ) diff --git a/libopenage/terrain/terrain_object.cpp b/libopenage/terrain/terrain_object.cpp index a8e3537304..840641d186 100644 --- a/libopenage/terrain/terrain_object.cpp +++ b/libopenage/terrain/terrain_object.cpp @@ -17,6 +17,7 @@ #include "terrain.h" #include "terrain_chunk.h" #include "terrain_outline.h" +#include "tile_range_serialization.h" namespace openage { @@ -116,7 +117,7 @@ bool TerrainObject::place(object_state init_state) { return true; } -bool TerrainObject::place(std::shared_ptr t, coord::phys3 &position, object_state init_state) { +bool TerrainObject::place(curve::CurveRecord &watcher, std::shared_ptr t, coord::phys3 &position, object_state init_state) { if (this->state != object_state::removed) { throw Error(MSG(err) << "This object has already been placed."); } @@ -130,14 +131,14 @@ bool TerrainObject::place(std::shared_ptr t, coord::phys3 &position, ob } // place on terrain - this->place_unchecked(t, position); + this->place_unchecked(watcher, t, position); // set state this->state = init_state; return true; } -bool TerrainObject::move(coord::phys3 &position) { +bool TerrainObject::move(curve::CurveRecord &watcher, coord::phys3 &position) { if (this->state == object_state::removed) { return false; } @@ -147,7 +148,7 @@ bool TerrainObject::move(coord::phys3 &position) { bool can_move = this->passable(position); if (can_move) { this->remove(); - this->place_unchecked(this->get_terrain(), position); + this->place_unchecked(watcher, this->get_terrain(), position); this->state = old_state; } return can_move; @@ -257,9 +258,10 @@ bool TerrainObject::operator <(const TerrainObject &other) { return true; } -void TerrainObject::place_unchecked(std::shared_ptr t, coord::phys3 &position) { +void TerrainObject::place_unchecked(curve::CurveRecord &watcher, std::shared_ptr t, coord::phys3 &position) { // storing the position: this->pos = get_range(position); + watcher.write_out(this->unit.id, this->pos, "pos"); this->terrain = t; this->occupied_chunk_count = 0; @@ -467,9 +469,9 @@ tile_range building_center(coord::phys3 west, coord::tile_delta size) { return result; } -bool complete_building(Unit &u) { +bool complete_building(curve::CurveRecord &watcher, Unit &u) { if (u.has_attribute(attr_type::building)) { - auto &build = u.get_attribute(); + auto &build = u.get_attribute(watcher); build.completed = 1.0f; // set ground under a completed building diff --git a/libopenage/terrain/terrain_object.h b/libopenage/terrain/terrain_object.h index 160b03ecd4..ead149e069 100644 --- a/libopenage/terrain/terrain_object.h +++ b/libopenage/terrain/terrain_object.h @@ -9,6 +9,8 @@ #include "../coord/tile.h" #include "../coord/phys3.h" +#include "tile_range.h" + namespace openage { class Terrain; @@ -16,6 +18,10 @@ class TerrainChunk; class Texture; class Unit; +namespace curve { +class CurveRecord; +} // namespace curve + /** * only placed will enable collision checks */ @@ -26,18 +32,6 @@ enum class object_state { placed_no_collision }; -/** - * A rectangle or square of tiles which is the minimim - * space to fit the units foundation or radius - * the end tile will have ne and se values greater or equal to - * the start tile - */ -struct tile_range { - coord::tile start; - coord::tile end; // start <= end - coord::phys3 draw; // gets used as center point of radial objects -}; - /** * get all tiles in the tile range -- useful for iterating * returns a flat list of tiles between the rectangle enclosed @@ -55,7 +49,7 @@ tile_range building_center(coord::phys3 west, coord::tile_delta size); /** * sets a building to a fully completed state */ -bool complete_building(Unit &); +bool complete_building(curve::CurveRecord &watcher, Unit &); /** * half a tile @@ -146,17 +140,18 @@ class TerrainObject : public std::enable_shared_from_this { /** * binds the TerrainObject to a certain TerrainChunk. * + * @param watcher: observes position change * @param terrain: the terrain where the object will be placed onto. * @param pos: (tile) position of the (nw,sw) corner * @param init_state should be floating, placed or placed_no_collision * @returns true when the object was placed, false when it did not fit at pos. */ - bool place(std::shared_ptr t, coord::phys3 &pos, object_state init_state); + bool place(curve::CurveRecord &watcher, std::shared_ptr t, coord::phys3 &pos, object_state init_state); /** * moves the object -- returns false if object cannot be moved here */ - bool move(coord::phys3 &pos); + bool move(curve::CurveRecord &watcher, coord::phys3 &pos); /** * remove this TerrainObject from the terrain chunks. @@ -275,7 +270,7 @@ class TerrainObject : public std::enable_shared_from_this { * otherwise the place function should be used * this does not modify the units placement state */ - void place_unchecked(std::shared_ptr t, coord::phys3 &position); + void place_unchecked(curve::CurveRecord &watcher, std::shared_ptr t, coord::phys3 &position); }; /** diff --git a/libopenage/terrain/tile_range.h b/libopenage/terrain/tile_range.h new file mode 100644 index 0000000000..dd773ce6e7 --- /dev/null +++ b/libopenage/terrain/tile_range.h @@ -0,0 +1,22 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../coord/tile.h" +#include "../coord/phys3.h" + +namespace openage { + +/** + * A rectangle or square of tiles which is the minimim + * space to fit the units foundation or radius + * the end tile will have ne and se values greater or equal to + * the start tile + */ +struct tile_range { + coord::tile start; + coord::tile end; // start <= end + coord::phys3 draw; // gets used as center point of radial objects +}; + +} diff --git a/libopenage/terrain/tile_range_serialization.cpp b/libopenage/terrain/tile_range_serialization.cpp new file mode 100644 index 0000000000..5ee70ac33a --- /dev/null +++ b/libopenage/terrain/tile_range_serialization.cpp @@ -0,0 +1,22 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#include "tile_range_serialization.h" + +#include "terrain_object.h" +#include "../coord/tile_serialization.h" +#include "../coord/phys3_serialization.h" + +namespace openage { + +std::ostream& operator<<(std::ostream &o, const tile_range &v) { + o << '(' << v.start << ',' << v.end << ',' << v.draw << ')'; + return o; +} + +std::istream& operator>>(std::istream &i, tile_range &v) { + char c; + i >> c >> v.start >> c >> v.end >> c >> v.draw >> c; + return i; +} + +} // openage diff --git a/libopenage/terrain/tile_range_serialization.h b/libopenage/terrain/tile_range_serialization.h new file mode 100644 index 0000000000..b4c8cade8d --- /dev/null +++ b/libopenage/terrain/tile_range_serialization.h @@ -0,0 +1,14 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +namespace openage { + +struct tile_range; + +std::ostream& operator<<(std::ostream &o, const tile_range &v); +std::istream& operator>>(std::istream &i, tile_range &v); + +} // openage diff --git a/libopenage/unit/ability.cpp b/libopenage/unit/ability.cpp index 2b0d2b94cc..e61afc973d 100644 --- a/libopenage/unit/ability.cpp +++ b/libopenage/unit/ability.cpp @@ -69,7 +69,7 @@ bool MoveAbility::can_invoke(Unit &to_modify, const Command &cmd) { return false; } -void MoveAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { +void MoveAbility::invoke(curve::CurveRecord&, Unit &to_modify, const Command &cmd, bool play_sound) { to_modify.log(MSG(dbg) << "invoke move action"); if (play_sound && this->sound) { this->sound->play(); @@ -101,8 +101,8 @@ bool SetPointAbility::can_invoke(Unit &to_modify, const Command &cmd) { to_modify.has_attribute(attr_type::building); } -void SetPointAbility::invoke(Unit &to_modify, const Command &cmd, bool) { - auto &build_attr = to_modify.get_attribute(); +void SetPointAbility::invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool) { + auto &build_attr = to_modify.get_attribute(watcher); build_attr.gather_point = cmd.position(); } @@ -130,7 +130,7 @@ bool GarrisonAbility::can_invoke(Unit &to_modify, const Command &cmd) { is_ally(to_modify, target); } -void GarrisonAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { +void GarrisonAbility::invoke(curve::CurveRecord&, Unit &to_modify, const Command &cmd, bool play_sound) { to_modify.log(MSG(dbg) << "invoke garrison action"); if (play_sound && this->sound) { this->sound->play(); @@ -151,7 +151,7 @@ bool UngarrisonAbility::can_invoke(Unit &to_modify, const Command &cmd) { return false; } -void UngarrisonAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { +void UngarrisonAbility::invoke(curve::CurveRecord&, Unit &to_modify, const Command &cmd, bool play_sound) { to_modify.log(MSG(dbg) << "invoke ungarrison action"); if (play_sound && this->sound) { this->sound->play(); @@ -174,7 +174,7 @@ bool TrainAbility::can_invoke(Unit &to_modify, const Command &cmd) { return false; } -void TrainAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { +void TrainAbility::invoke(curve::CurveRecord&, Unit &to_modify, const Command &cmd, bool play_sound) { to_modify.log(MSG(dbg) << "invoke train action"); if (play_sound && this->sound) { this->sound->play(); @@ -198,14 +198,14 @@ bool BuildAbility::can_invoke(Unit &to_modify, const Command &cmd) { return false; } -void BuildAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { +void BuildAbility::invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound) { to_modify.log(MSG(dbg) << "invoke build action"); if (play_sound && this->sound) { this->sound->play(); } if (cmd.has_unit()) { - to_modify.push_action(std::make_unique(&to_modify, cmd.unit()->get_ref())); + to_modify.push_action(std::make_unique(&to_modify, cmd.unit()->get_ref(), watcher)); } } @@ -225,7 +225,7 @@ bool GatherAbility::can_invoke(Unit &to_modify, const Command &cmd) { return false; } -void GatherAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { +void GatherAbility::invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound) { to_modify.log(MSG(dbg) << "invoke gather action"); if (play_sound && this->sound) { this->sound->play(); @@ -233,7 +233,7 @@ void GatherAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) Unit *target = cmd.unit(); try { - to_modify.push_action(std::make_unique(&to_modify, target->get_ref())); + to_modify.push_action(std::make_unique(&to_modify, target->get_ref(), watcher)); } catch (const std::invalid_argument &e) { to_modify.log(MSG(dbg) << "invoke gather action cancelled due to an exception. Reason: " << e.what()); } @@ -260,14 +260,14 @@ bool AttackAbility::can_invoke(Unit &to_modify, const Command &cmd) { return false; } -void AttackAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { +void AttackAbility::invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound) { to_modify.log(MSG(dbg) << "invoke attack action"); if (play_sound && this->sound) { this->sound->play(); } Unit *target = cmd.unit(); - to_modify.push_action(std::make_unique(&to_modify, target->get_ref())); + to_modify.push_action(std::make_unique(watcher, &to_modify, target->get_ref())); } ability_set UnitAbility::set_from_list(const std::vector &items) { diff --git a/libopenage/unit/ability.h b/libopenage/unit/ability.h index cbd2930390..2047d91620 100644 --- a/libopenage/unit/ability.h +++ b/libopenage/unit/ability.h @@ -20,6 +20,10 @@ class UnitAction; class UnitTexture; class UnitType; +namespace curve { +class CurveRecord; +} // namespace curve + /** * roughly the same as command_ability in game data */ @@ -95,7 +99,7 @@ class UnitAbility { /** * applies command to a given unit */ - virtual void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) = 0; + virtual void invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound=false) = 0; /** * some common functions @@ -127,7 +131,7 @@ class MoveAbility: public UnitAbility { bool can_invoke(Unit &to_modify, const Command &cmd) override; - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; + void invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound=false) override; private: const Sound *sound; @@ -146,7 +150,7 @@ class SetPointAbility: public UnitAbility { bool can_invoke(Unit &to_modify, const Command &cmd) override; - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; + void invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound=false) override; }; @@ -163,7 +167,7 @@ class GarrisonAbility: public UnitAbility { bool can_invoke(Unit &to_modify, const Command &cmd) override; - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; + void invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound=false) override; private: const Sound *sound; @@ -182,7 +186,7 @@ class UngarrisonAbility: public UnitAbility { bool can_invoke(Unit &to_modify, const Command &cmd) override; - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; + void invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound=false) override; private: const Sound *sound; @@ -201,7 +205,7 @@ class TrainAbility: public UnitAbility { bool can_invoke(Unit &to_modify, const Command &cmd) override; - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; + void invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound=false) override; private: const Sound *sound; @@ -220,7 +224,7 @@ class BuildAbility: public UnitAbility { bool can_invoke(Unit &to_modify, const Command &cmd) override; - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; + void invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound=false) override; private: const Sound *sound; @@ -239,7 +243,7 @@ class GatherAbility: public UnitAbility { bool can_invoke(Unit &to_modify, const Command &cmd) override; - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; + void invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound=false) override; private: const Sound *sound; @@ -258,7 +262,7 @@ class AttackAbility: public UnitAbility { bool can_invoke(Unit &to_modify, const Command &cmd) override; - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; + void invoke(curve::CurveRecord &watcher, Unit &to_modify, const Command &cmd, bool play_sound=false) override; private: const Sound *sound; diff --git a/libopenage/unit/action.cpp b/libopenage/unit/action.cpp index 93649f5c37..8b1987a864 100644 --- a/libopenage/unit/action.cpp +++ b/libopenage/unit/action.cpp @@ -89,17 +89,17 @@ void UnitAction::draw_debug() { } } -void UnitAction::face_towards(const coord::phys3 pos) { +void UnitAction::face_towards(curve::CurveRecord &watcher, const coord::phys3 pos) { if (this->entity->has_attribute(attr_type::direction)) { - auto &d_attr = this->entity->get_attribute(); + auto d_attr = this->entity->get_attribute(watcher); d_attr.unit_dir = pos - this->entity->location->pos.draw; } } // TODO remove (keep for testing) -void UnitAction::damage_object(Unit &target, unsigned dmg) { +void UnitAction::damage_object(curve::CurveRecord &watcher, Unit &target, unsigned dmg) { if (target.has_attribute(attr_type::hitpoints)) { - auto &hp = target.get_attribute(); + auto hp = target.get_attribute(watcher); if (hp.current > dmg) { hp.current -= dmg; } @@ -109,13 +109,13 @@ void UnitAction::damage_object(Unit &target, unsigned dmg) { } } -void UnitAction::damage_object(Unit &target) { +void UnitAction::damage_object(curve::CurveRecord &watcher, Unit &target) { if (target.has_attribute(attr_type::hitpoints)) { - auto &hp = target.get_attribute(); + auto hp = target.get_attribute(watcher); if (target.has_attribute(attr_type::armor) && this->entity->has_attribute(attr_type::attack)) { - auto &armor = target.get_attribute().armor; - auto &damage = this->entity->get_attribute().damage; + const auto &armor = target.get_attribute().armor; + const auto &damage = this->entity->get_attribute().damage; unsigned int actual_damage = 0; for (const auto &pair : armor) { @@ -140,13 +140,13 @@ void UnitAction::damage_object(Unit &target) { } else { // TODO remove (keep for testing) - damage_object(target, 1); + damage_object(watcher, target, 1); } } } -void UnitAction::move_to(Unit &target, bool use_range) { - auto &player = this->entity->get_attribute().player; +void UnitAction::move_to(curve::CurveRecord &watcher, Unit &target, bool use_range) { + auto &player = this->entity->get_attribute(watcher).player; Command cmd(player, &target); cmd.set_ability(ability_type::move); if (use_range) { @@ -179,7 +179,7 @@ TargetAction::TargetAction(Unit *u, graphic_type gt, UnitReference r) TargetAction(u, gt, r, adjacent_range(u)) { } -void TargetAction::update(unsigned int time) { +void TargetAction::update(curve::CurveRecord &watcher, unsigned int time) { auto target_ptr = update_distance(); if (!target_ptr) { return; // target has become invalid @@ -190,21 +190,21 @@ void TargetAction::update(unsigned int time) { // derived from TargetAction // set direction unit should face - this->face_towards(target_ptr->location->pos.draw); + this->face_towards(watcher, target_ptr->location->pos.draw); // move to within the set radius if (this->dist_to_target <= this->radius) { // the derived class controls what to // do when in range of the target - this->update_in_range(time, target_ptr); + this->update_in_range(watcher, time, target_ptr); this->repath_attempts = 10; } else if (this->repath_attempts) { // out of range so try move towards // if this unit has a move ability - this->move_to(*target_ptr); + this->move_to(watcher, *target_ptr); this->repath_attempts -= 1; } else { @@ -294,7 +294,7 @@ DecayAction::DecayAction(Unit *e) } } -void DecayAction::update(unsigned int time) { +void DecayAction::update(curve::CurveRecord&, unsigned int time) { this->frame += time * this->frame_rate / 10000.0f; } @@ -316,9 +316,9 @@ DeadAction::DeadAction(Unit *e, std::function on_complete) } } -void DeadAction::update(unsigned int time) { +void DeadAction::update(curve::CurveRecord &watcher, unsigned int time) { if (this->entity->has_attribute(attr_type::hitpoints)) { - auto &h_attr = this->entity->get_attribute(); + auto h_attr = this->entity->get_attribute(watcher); h_attr.current = 0; } @@ -353,7 +353,7 @@ FoundationAction::FoundationAction(Unit *e, bool add_destruction) cancel{false} { } -void FoundationAction::update(unsigned int) { +void FoundationAction::update(curve::CurveRecord&, unsigned int) { if (!this->entity->location) { this->cancel = true; } @@ -390,7 +390,7 @@ IdleAction::IdleAction(Unit *e) this->auto_abilities = UnitAbility::set_from_list({ability_type::attack, ability_type::heal}); } -void IdleAction::update(unsigned int time) { +void IdleAction::update(curve::CurveRecord&, unsigned int time) { // auto task searching if (this->entity->location && @@ -497,7 +497,7 @@ void MoveAction::initialise() { MoveAction::~MoveAction() {} -void MoveAction::update(unsigned int time) { +void MoveAction::update(curve::CurveRecord &watcher, unsigned int time) { if (this->unit_target.is_valid()) { // a unit is targeted, which may move auto &target_object = this->unit_target.get()->location; @@ -507,8 +507,8 @@ void MoveAction::update(unsigned int time) { this->end_action = true; return; } - coord::phys3 &target_pos = target_object->pos.draw; - coord::phys3 &unit_pos = this->entity->location->pos.draw; + const coord::phys3 &target_pos = target_object->pos.draw; + const coord::phys3 &unit_pos = this->entity->location->pos.draw; // repath if target changes tiles by a threshold // this repathing is more frequent when the unit is @@ -533,13 +533,13 @@ void MoveAction::update(unsigned int time) { } // find distance to move in this update - auto &sp_attr = this->entity->get_attribute(); + auto &sp_attr = this->entity->get_attribute(watcher); coord::phys_t distance_to_move = sp_attr.unit_speed * time; // current position and direction coord::phys3 new_position = this->entity->location->pos.draw; - auto &d_attr = this->entity->get_attribute(); - coord::phys3_delta new_direction = d_attr.unit_dir; + auto d_attr = this->entity->get_attribute(watcher); + coord::phys3_delta new_direction = coord::phys3_delta{(coord::phys_t)d_attr.unit_dir.ne, (coord::phys_t)d_attr.unit_dir.se, (coord::phys_t)d_attr.unit_dir.up}; while (distance_to_move > 0) { if (this->path.waypoints.empty()) { @@ -575,7 +575,7 @@ void MoveAction::update(unsigned int time) { } // check move collisions - bool move_completed = this->entity->location->move(new_position); + bool move_completed = this->entity->location->move(watcher, new_position); if (move_completed) { d_attr.unit_dir = new_direction; this->set_distance(); @@ -638,7 +638,7 @@ void MoveAction::set_path() { void MoveAction::set_distance() { if (this->unit_target.is_valid()) { auto &target_object = this->unit_target.get()->location; - coord::phys3 &unit_pos = this->entity->location->pos.draw; + const coord::phys3 &unit_pos = this->entity->location->pos.draw; this->distance_to_target = target_object->from_edge(unit_pos); } else { @@ -653,8 +653,8 @@ GarrisonAction::GarrisonAction(Unit *e, UnitReference build) complete{false} { } -void GarrisonAction::update_in_range(unsigned int, Unit *target_unit) { - auto &garrison_attr = target_unit->get_attribute(); +void GarrisonAction::update_in_range(curve::CurveRecord &watcher, unsigned int, Unit *target_unit) { + auto &garrison_attr = target_unit->get_attribute(watcher); garrison_attr.content.push_back(this->entity->get_ref()); if (this->entity->location) { @@ -671,24 +671,24 @@ UngarrisonAction::UngarrisonAction(Unit *e, const coord::phys3 &pos) complete{false} { } -void UngarrisonAction::update(unsigned int) { - auto &garrison_attr = this->entity->get_attribute(); +void UngarrisonAction::update(curve::CurveRecord &watcher, unsigned int) { + auto &garrison_attr = this->entity->get_attribute(watcher); // try unload all objects currently garrisoned auto position_it = std::remove_if( std::begin(garrison_attr.content), std::end(garrison_attr.content), - [this](UnitReference &u) { + [this, &watcher](UnitReference &u) { if (u.is_valid()) { // ptr to unit being ungarrisoned Unit *unit_ptr = u.get(); // make sure it was placed outside - if (unit_ptr->unit_type->place_beside(unit_ptr, this->entity->location.get())) { + if (unit_ptr->unit_type->place_beside(watcher, unit_ptr, this->entity->location.get())) { // task unit to move to position - auto &player = this->entity->get_attribute().player; + auto &player = this->entity->get_attribute(watcher).player; Command cmd(player, this->position); cmd.set_ability(ability_type::move); unit_ptr->queue_cmd(cmd); @@ -715,15 +715,15 @@ TrainAction::TrainAction(Unit *e, UnitType *pp) train_percent{.0f} { } -void TrainAction::update(unsigned int time) { +void TrainAction::update(curve::CurveRecord &watcher, unsigned int time) { // place unit when ready if (this->train_percent > 1.0f) { // create using the producer UnitContainer *container = this->entity->get_container(); - auto &player = this->entity->get_attribute().player; - auto uref = container->new_unit(*this->trained, player, this->entity->location.get()); + auto &player = this->entity->get_attribute(watcher).player; + auto uref = container->new_unit(watcher, *this->trained, player, this->entity->location.get()); // make sure unit got placed // try again next update if cannot place @@ -731,7 +731,7 @@ void TrainAction::update(unsigned int time) { if (this->entity->has_attribute(attr_type::building)) { // use a move command to the gather point - auto &build_attr = this->entity->get_attribute(); + auto &build_attr = this->entity->get_attribute(watcher); Command cmd(player, build_attr.gather_point); cmd.set_ability(ability_type::move); uref.get()->queue_cmd(cmd); @@ -746,25 +746,25 @@ void TrainAction::update(unsigned int time) { void TrainAction::on_completion() {} -BuildAction::BuildAction(Unit *e, UnitReference foundation) +BuildAction::BuildAction(Unit *e, UnitReference foundation, curve::CurveRecord &watcher) : TargetAction{e, graphic_type::work, foundation}, complete{.0f}, build_rate{.0001f} { // update the units type - auto &gatherer_attr = this->entity->get_attribute(); + auto &gatherer_attr = this->entity->get_attribute(watcher); auto building_class = gamedata::unit_classes::BUILDING; if (gatherer_attr.graphics.count(building_class) > 0) { auto *new_type = gatherer_attr.graphics.at(building_class); - auto &pl_attr = this->entity->get_attribute(); - new_type->initialise(this->entity, pl_attr.player); + auto &pl_attr = this->entity->get_attribute(watcher); + new_type->initialise(watcher, this->entity, pl_attr.player); } } -void BuildAction::update_in_range(unsigned int time, Unit *target_unit) { +void BuildAction::update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_unit) { if (target_unit->has_attribute(attr_type::building)) { - auto &build = target_unit->get_attribute(); + auto &build = target_unit->get_attribute(watcher); // upgrade floating outlines auto target_location = target_unit->location.get(); @@ -808,14 +808,14 @@ const graphic_set &BuildAction::current_graphics() const { // the gatherer attributes attached to the unit // are used to modify the graphic - auto &gatherer_attr = this->entity->get_attribute(); + const auto &gatherer_attr = this->entity->get_attribute(); if (this->get_target().is_valid() && this->get_target().get()->has_attribute(attr_type::building)) { // set builder graphics if available if (gatherer_attr.graphics.count(gamedata::unit_classes::BUILDING) > 0) { - return gatherer_attr.graphics[gamedata::unit_classes::BUILDING]->graphics; + return gatherer_attr.graphics.at(gamedata::unit_classes::BUILDING)->graphics; } } } @@ -828,9 +828,9 @@ RepairAction::RepairAction(Unit *e, UnitReference tar) complete{false} { } -void RepairAction::update_in_range(unsigned int, Unit *) {} +void RepairAction::update_in_range(curve::CurveRecord &, unsigned int, Unit *) {} -GatherAction::GatherAction(Unit *e, UnitReference tar) +GatherAction::GatherAction(Unit *e, UnitReference tar, curve::CurveRecord &watcher) : TargetAction{e, graphic_type::work, tar}, complete{false}, @@ -839,20 +839,20 @@ GatherAction::GatherAction(Unit *e, UnitReference tar) Unit *target = this->target.get(); this->resource_class = target->unit_type->unit_class; - auto &gatherer_attr = this->entity->get_attribute(); + auto &gatherer_attr = this->entity->get_attribute(watcher); // handle unit type changes based on resource class if (gatherer_attr.graphics.count(this->resource_class) > 0) { auto *new_type = gatherer_attr.graphics.at(this->resource_class); - auto &pl_attr = this->entity->get_attribute(); - new_type->initialise(this->entity, pl_attr.player); + auto &pl_attr = this->entity->get_attribute(watcher); + new_type->initialise(watcher, this->entity, pl_attr.player); } // set the type of gatherer if (target->has_attribute(attr_type::resource)) { - auto &resource_attr = target->get_attribute(); + auto &resource_attr = target->get_attribute(watcher); if (gatherer_attr.current_type != resource_attr.resource_type) { - this->entity->get_attribute().amount = 0; + this->entity->get_attribute(watcher).amount = 0; } gatherer_attr.current_type = resource_attr.resource_type; } else { @@ -862,8 +862,8 @@ GatherAction::GatherAction(Unit *e, UnitReference tar) GatherAction::~GatherAction() {} -void GatherAction::update_in_range(unsigned int time, Unit *targeted_resource) { - auto &gatherer_attr = this->entity->get_attribute(); +void GatherAction::update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *targeted_resource) { + auto &gatherer_attr = this->entity->get_attribute(watcher); if (this->target_resource) { // the targets attributes @@ -897,7 +897,7 @@ void GatherAction::update_in_range(unsigned int time, Unit *targeted_resource) { } else { - auto &resource_attr = targeted_resource->get_attribute(); + auto &resource_attr = targeted_resource->get_attribute(watcher); if (resource_attr.amount <= 0.0f) { // when the resource runs out @@ -921,7 +921,7 @@ void GatherAction::update_in_range(unsigned int time, Unit *targeted_resource) { // dropsite has been reached // add value to player stockpile - Player &player = this->entity->get_attribute().player; + Player &player = this->entity->get_attribute(watcher).player; player.receive(gatherer_attr.current_type, gatherer_attr.amount); gatherer_attr.amount = 0.0f; @@ -974,7 +974,7 @@ const graphic_set &GatherAction::current_graphics() const { return this->entity->unit_type->graphics; } -AttackAction::AttackAction(Unit *e, UnitReference tar) +AttackAction::AttackAction(curve::CurveRecord &watcher, Unit *e, UnitReference tar) : TargetAction{e, graphic_type::attack, tar, get_attack_range(e)}, strike_percent{0.0f}, @@ -985,19 +985,19 @@ AttackAction::AttackAction(Unit *e, UnitReference tar) !tar.get()->has_attribute(attr_type::resource)) { auto &att_attr = this->entity->get_attribute(); auto &pl_attr = this->entity->get_attribute(); - att_attr.attack_type->initialise(this->entity, pl_attr.player); + att_attr.attack_type->initialise(watcher, this->entity, pl_attr.player); } } AttackAction::~AttackAction() {} -void AttackAction::update_in_range(unsigned int time, Unit *target_ptr) { +void AttackAction::update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_ptr) { if (this->strike_percent > 0.0) { this->strike_percent -= this->rate_of_fire * time; } else { this->strike_percent += 1.0f; - this->attack(*target_ptr); + this->attack(watcher, *target_ptr); } // inc frame @@ -1009,19 +1009,19 @@ bool AttackAction::completed_in_range(Unit *target_ptr) const { return h_attr.current < 1; // is unit still alive? } -void AttackAction::attack(Unit &target) { - auto &attack = this->entity->get_attribute(); +void AttackAction::attack(curve::CurveRecord &watcher, Unit &target) { + const auto &attack = this->entity->get_attribute(); if (attack.ptype) { // add projectile to the game - this->fire_projectile(attack, target.location->pos.draw); + this->fire_projectile(watcher, attack, target.location->pos.draw); } else { - this->damage_object(target); + this->damage_object(watcher, target); } } -void AttackAction::fire_projectile(const Attribute &att, const coord::phys3 &target) { +void AttackAction::fire_projectile(curve::CurveRecord &watcher, const Attribute &att, const coord::phys3 &target) { // container terrain and initial position UnitContainer *container = this->entity->get_container(); @@ -1029,16 +1029,16 @@ void AttackAction::fire_projectile(const Attribute &att, cons current_pos.up = att.init_height; // create using the producer - auto player = this->entity->get_attribute().player; - auto projectile_ref = container->new_unit(*att.ptype, player, current_pos); + auto player = this->entity->get_attribute(watcher).player; + auto projectile_ref = container->new_unit(watcher, *att.ptype, player, current_pos); // send towards target using a projectile ability (creates projectile motion action) if (projectile_ref.is_valid()) { auto projectile = projectile_ref.get(); - auto &projectile_attr = projectile->get_attribute(); + auto &projectile_attr = projectile->get_attribute(watcher); projectile_attr.launcher = this->entity->get_ref(); projectile_attr.launched = true; - projectile->push_action(std::make_unique(projectile, target), true); + projectile->push_action(std::make_unique(watcher, projectile, target), true); } else { this->entity->log(MSG(dbg) << "projectile launch failed"); @@ -1055,15 +1055,15 @@ HealAction::HealAction(Unit *e, UnitReference tar) HealAction::~HealAction() {} -void HealAction::update_in_range(unsigned int time, Unit *target_ptr) { - auto &heal = this->entity->get_attribute(); +void HealAction::update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_ptr) { + auto heal = this->entity->get_attribute(watcher); if (this->heal_percent > 0.0) { this->heal_percent -= heal.rate * time; } else { this->heal_percent += 1.0f; - this->heal(*target_ptr); + this->heal(watcher, *target_ptr); } // inc frame @@ -1075,13 +1075,13 @@ bool HealAction::completed_in_range(Unit *target_ptr) const { return h_attr.current >= h_attr.max; // is unit at full hitpoints? } -void HealAction::heal(Unit &target) { +void HealAction::heal(curve::CurveRecord &watcher, Unit &target) { auto &heal = this->entity->get_attribute(); // TODO move to seperate function heal_object (like damage_object)? // heal object if (target.has_attribute(attr_type::hitpoints)) { - auto &hp = target.get_attribute(); + auto hp = target.get_attribute(watcher); if ((hp.current + heal.life) < hp.max) { hp.current += heal.life; } @@ -1099,9 +1099,9 @@ ConvertAction::ConvertAction(Unit *e, UnitReference tar) complete{.0f} { } -void ConvertAction::update_in_range(unsigned int, Unit *) {} +void ConvertAction::update_in_range(curve::CurveRecord &, unsigned int, Unit *) {} -ProjectileAction::ProjectileAction(Unit *e, coord::phys3 target) +ProjectileAction::ProjectileAction(curve::CurveRecord &watcher, Unit *e, coord::phys3 target) : UnitAction{e, graphic_type::standing}, has_hit{false} { @@ -1111,7 +1111,7 @@ ProjectileAction::ProjectileAction(Unit *e, coord::phys3 target) coord::phys_t projectile_speed = sp_attr.unit_speed; // arc of projectile - auto &pr_attr = this->entity->get_attribute(); + const auto &pr_attr = this->entity->get_attribute(); float projectile_arc = pr_attr.projectile_arc; // distance and time to target @@ -1131,7 +1131,7 @@ ProjectileAction::ProjectileAction(Unit *e, coord::phys3 target) this->grav = 0.01f * (exp(pow(projectile_arc, 0.5f)) - 1) * projectile_speed; // inital launch direction - auto &d_attr = this->entity->get_attribute(); + auto d_attr = this->entity->get_attribute(watcher); d_attr.unit_dir = (projectile_speed * d) / distance_to_target; // account for initial height @@ -1141,14 +1141,14 @@ ProjectileAction::ProjectileAction(Unit *e, coord::phys3 target) ProjectileAction::~ProjectileAction() {} -void ProjectileAction::update(unsigned int time) { - auto &d_attr = this->entity->get_attribute(); +void ProjectileAction::update(curve::CurveRecord &watcher, unsigned int time) { + auto d_attr = this->entity->get_attribute(watcher); // apply gravity d_attr.unit_dir.up -= this->grav * time; coord::phys3 new_position = this->entity->location->pos.draw + d_attr.unit_dir * time; - if (!this->entity->location->move(new_position)) { + if (!this->entity->location->move(watcher, new_position)) { // find object which was hit auto terrain = this->entity->location->get_terrain(); @@ -1157,7 +1157,7 @@ void ProjectileAction::update(unsigned int time) { for (auto obj_location : tc->obj) { if (this->entity->location.get() != obj_location && obj_location->check_collisions()) { - this->damage_object(obj_location->unit, 1); + this->damage_object(watcher, obj_location->unit, 1); break; } } diff --git a/libopenage/unit/action.h b/libopenage/unit/action.h index 99e389f1f2..5032bf9515 100644 --- a/libopenage/unit/action.h +++ b/libopenage/unit/action.h @@ -44,7 +44,7 @@ class UnitAction { * each action has its own update functionality which gets called when this * is the active action */ - virtual void update(unsigned int) = 0; + virtual void update(curve::CurveRecord &watcher, unsigned int) = 0; /** * action to perform when popped from a units action stack @@ -93,10 +93,10 @@ class UnitAction { /** * common functions for actions */ - void face_towards(const coord::phys3 pos); - void damage_object(Unit &target, unsigned dmg); // TODO remove (keep for testing) - void damage_object(Unit &target); - void move_to(Unit &target, bool use_range=true); + void face_towards(curve::CurveRecord &watcher, const coord::phys3 pos); + void damage_object(curve::CurveRecord &watcher, Unit &target, unsigned dmg); // TODO remove (keep for testing) + void damage_object(curve::CurveRecord &watcher, Unit &target); + void move_to(curve::CurveRecord &watcher, Unit &target, bool use_range=true); /** * produce debug info such as visualising paths @@ -161,7 +161,7 @@ class TargetAction: public UnitAction { TargetAction(Unit *e, graphic_type gt, UnitReference r); virtual ~TargetAction() {} - void update(unsigned int) override; + void update(curve::CurveRecord &watcher, unsigned int) override; void on_completion() override; bool completed() const override; bool allow_interupt() const override { return true; } @@ -171,7 +171,7 @@ class TargetAction: public UnitAction { /** * Control units action when in range of the target */ - virtual void update_in_range(unsigned int, Unit *) = 0; + virtual void update_in_range(curve::CurveRecord &watcher, unsigned int, Unit *) = 0; virtual bool completed_in_range(Unit *) const = 0; coord::phys_t distance_to_target(); @@ -202,7 +202,7 @@ class DecayAction: public UnitAction { DecayAction(Unit *e); virtual ~DecayAction() {} - void update(unsigned int) override; + void update(curve::CurveRecord &watcher, unsigned int) override; void on_completion() override; bool completed() const override; bool allow_interupt() const override { return false; } @@ -222,7 +222,7 @@ class DeadAction: public UnitAction { DeadAction(Unit *e, std::function on_complete=[]() {}); virtual ~DeadAction() {} - void update(unsigned int) override; + void update(curve::CurveRecord &watcher, unsigned int) override; void on_completion() override; bool completed() const override; bool allow_interupt() const override { return false; } @@ -243,7 +243,7 @@ class FoundationAction: public UnitAction { FoundationAction(Unit *e, bool add_destuction=false); virtual ~FoundationAction() {} - void update(unsigned int) override; + void update(curve::CurveRecord &watcher, unsigned int) override; void on_completion() override; bool completed() const override; bool allow_interupt() const override { return true; } @@ -263,7 +263,7 @@ class IdleAction: public UnitAction { IdleAction(Unit *e); virtual ~IdleAction() {} - void update(unsigned int) override; + void update(curve::CurveRecord &watcher, unsigned int) override; void on_completion() override; bool completed() const override; bool allow_interupt() const override { return false; } @@ -293,7 +293,7 @@ class MoveAction: public UnitAction { MoveAction(Unit *e, UnitReference tar, coord::phys_t within_range); virtual ~MoveAction(); - void update(unsigned int) override; + void update(curve::CurveRecord &watcher, unsigned int) override; void on_completion() override; bool completed() const override; bool allow_interupt() const override { return true; } @@ -335,7 +335,7 @@ class GarrisonAction: public TargetAction { GarrisonAction(Unit *e, UnitReference build); virtual ~GarrisonAction() {} - void update_in_range(unsigned int time, Unit *target_unit) override; + void update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_unit) override; bool completed_in_range(Unit *) const override { return this->complete; } std::string name() const override { return "garrison"; } @@ -351,7 +351,7 @@ class UngarrisonAction: public UnitAction { UngarrisonAction(Unit *e, const coord::phys3 &pos); virtual ~UngarrisonAction() {} - void update(unsigned int) override; + void update(curve::CurveRecord &watcher, unsigned int) override; void on_completion() override; bool completed() const override { return this->complete; } bool allow_interupt() const override { return true; } @@ -371,7 +371,7 @@ class TrainAction: public UnitAction { TrainAction(Unit *e, UnitType *pp); virtual ~TrainAction() {} - void update(unsigned int) override; + void update(curve::CurveRecord &watcher, unsigned int) override; void on_completion() override; bool completed() const override { return this->complete; } bool allow_interupt() const override { return false; } @@ -389,10 +389,10 @@ class TrainAction: public UnitAction { */ class BuildAction: public TargetAction { public: - BuildAction(Unit *e, UnitReference foundation); + BuildAction(Unit *e, UnitReference foundation, curve::CurveRecord &watcher); virtual ~BuildAction() {} - void update_in_range(unsigned int time, Unit *target_unit) override; + void update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_unit) override; bool completed_in_range(Unit *) const override { return this->complete >= 1.0f; } std::string name() const override { return "build"; } const graphic_set ¤t_graphics() const override; @@ -410,7 +410,7 @@ class RepairAction: public TargetAction { RepairAction(Unit *e, UnitReference tar); virtual ~RepairAction() {} - void update_in_range(unsigned int time, Unit *target_unit) override; + void update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_unit) override; bool completed_in_range(Unit *) const override { return this->complete; } std::string name() const override { return "repair"; } @@ -424,10 +424,10 @@ class RepairAction: public TargetAction { */ class GatherAction: public TargetAction { public: - GatherAction(Unit *e, UnitReference tar); + GatherAction(Unit *e, UnitReference tar, curve::CurveRecord &watcher); virtual ~GatherAction(); - void update_in_range(unsigned int time, Unit *target_unit) override; + void update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_unit) override; bool completed_in_range(Unit *) const override { return this->complete; } std::string name() const override { return "gather"; } const graphic_set ¤t_graphics() const override; @@ -444,10 +444,10 @@ class GatherAction: public TargetAction { */ class AttackAction: public TargetAction { public: - AttackAction(Unit *e, UnitReference tar); + AttackAction(curve::CurveRecord &watcher, Unit *e, UnitReference tar); virtual ~AttackAction(); - void update_in_range(unsigned int time, Unit *target_unit) override; + void update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_unit) override; bool completed_in_range(Unit *) const override; std::string name() const override { return "attack"; } @@ -457,12 +457,12 @@ class AttackAction: public TargetAction { /** * use attack action */ - void attack(Unit &target); + void attack(curve::CurveRecord &watcher, Unit &target); /** * add a projectile game object which moves towards the target */ - void fire_projectile(const Attribute &att, const coord::phys3 &target); + void fire_projectile(curve::CurveRecord &watcher, const Attribute &att, const coord::phys3 &target); }; /** @@ -473,7 +473,7 @@ class HealAction: public TargetAction { HealAction(Unit *e, UnitReference tar); virtual ~HealAction(); - void update_in_range(unsigned int time, Unit *target_unit) override; + void update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_unit) override; bool completed_in_range(Unit *) const override; std::string name() const override { return "heal"; } @@ -483,7 +483,7 @@ class HealAction: public TargetAction { /** * use heal action */ - void heal(Unit &target); + void heal(curve::CurveRecord &watcher, Unit &target); }; /** @@ -494,7 +494,7 @@ class ConvertAction: public TargetAction { ConvertAction(Unit *e, UnitReference tar); virtual ~ConvertAction() {} - void update_in_range(unsigned int time, Unit *target_unit) override; + void update_in_range(curve::CurveRecord &watcher, unsigned int time, Unit *target_unit) override; bool completed_in_range(Unit *) const override { return this->complete >= 1.0f; } std::string name() const override { return "convert"; } @@ -508,10 +508,10 @@ class ConvertAction: public TargetAction { */ class ProjectileAction: public UnitAction { public: - ProjectileAction(Unit *e, coord::phys3 target); + ProjectileAction(curve::CurveRecord &watcher, Unit *e, coord::phys3 target); virtual ~ProjectileAction(); - void update(unsigned int) override; + void update(curve::CurveRecord &watcher, unsigned int) override; void on_completion() override; bool completed() const override; bool allow_interupt() const override { return false; } diff --git a/libopenage/unit/attribute.h b/libopenage/unit/attribute.h index 97cfa42048..22b24d9f85 100644 --- a/libopenage/unit/attribute.h +++ b/libopenage/unit/attribute.h @@ -11,6 +11,7 @@ #include "../terrain/terrain_object.h" #include "../gamestate/resource.h" #include "unit_container.h" +#include "../curve/curve_record_replay.h" namespace std { @@ -69,7 +70,8 @@ enum class attr_type { dropsite, resource, gatherer, - garrison + garrison, + position }; enum class attack_stance { @@ -124,8 +126,15 @@ using typeamount_map = std::unordered_map; /** * return attribute from a container */ -template Attribute get_attr(attr_map_t &map) { - return *reinterpret_cast *>(map[T]); +template Attribute& get_attr_ref(attr_map_t &map) { + return *reinterpret_cast *>(map[T].get()); +} + +/** + * return attribute as constant from a container + */ +template const Attribute& get_attr_ref(const attr_map_t &map) { + return *reinterpret_cast *>(map.at(T).get()); } // ----------------------------- @@ -154,11 +163,13 @@ template<> class Attribute: public AttributeContainer { template<> class Attribute: public AttributeContainer { public: - Attribute(unsigned int i) + Attribute(curve::CurveRecord &watcher, id_t id, unsigned int i) : AttributeContainer{attr_type::hitpoints}, current{i}, - max{i} {} + max{i} { + watcher.write_out(id, i, "hitpoints"); + } bool shared() const override { return false; @@ -362,7 +373,7 @@ template<> class Attribute: public AttributeContainer { return std::make_shared>(*this); } - bool accepting_resource(game_resource res) { + bool accepting_resource(game_resource res) const { if (std::find(resource_types.begin(), resource_types.end(), res) != resource_types.end()) { return true; } else { diff --git a/libopenage/unit/attribute_setter.h b/libopenage/unit/attribute_setter.h new file mode 100644 index 0000000000..b830bfc988 --- /dev/null +++ b/libopenage/unit/attribute_setter.h @@ -0,0 +1,45 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "attribute_spy.h" +#include "unit.h" + +namespace openage { + +using AttributeSetter = void(*)(std::istream&, id_t, UnitContainer&); + +template +void apply_to_attr(std::istream &stream, id_t id, UnitContainer &placed_units) { + AttributeSpy::val_ref(placed_units.get_unit(id).get()->get_attribute_unwatched()) = parse_attr(stream); +} + +template +struct ApplyToAttr { + static constexpr std::tuple named_setter = std::make_tuple(AttributeSpy::name, apply_to_attr); +}; + +template<> +void apply_to_attr(std::istream &stream, id_t id, UnitContainer &placed_units) { + placed_units.get_unit(id).get()->location->pos = parse_attr(stream); +} + +const std::tuple parse_funcs[] = { + ApplyToAttr::named_setter, + ApplyToAttr::named_setter, + ApplyToAttr::named_setter, +}; + +void read_attr(std::istream &stream, id_t id, UnitContainer &placed_units) { + const auto attr_name = parse_attr_name(stream); + const auto foundIt = std::find_if(std::begin(parse_funcs), std::end(parse_funcs), [&attr_name](const std::tuple &named_setter) { + return std::get(named_setter) == attr_name; + }); + + if (foundIt != std::end(parse_funcs)) + (std::get(*foundIt))(stream, id, placed_units); +} + +} // namespace openage diff --git a/libopenage/unit/attribute_spy.h b/libopenage/unit/attribute_spy.h new file mode 100644 index 0000000000..fbd7b9857d --- /dev/null +++ b/libopenage/unit/attribute_spy.h @@ -0,0 +1,206 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "../coord/phys3.h" +#include "../coord/phys3_serialization.h" +#include "../terrain/tile_range.h" +#include "../terrain/tile_range_serialization.h" +#include "attribute.h" +#include "attribute_watcher.h" + +namespace openage { + +template +class Spied { +public: + using type = T; + + Spied(T &value, std::function write_out) + : + value(value), + write_out{write_out} { + } + + Spied(T &value, curve::CurveRecord &watcher, id_t id, const char *name) + : + value(value), + write_out{[&watcher, id, &name](T value) { watcher.write_out(id, value, name); }} { + } + + operator T() const { + return this->value; + } + + Spied& operator=(T value) { + this->value = value; + this->write_out(value); + return *this; + } + + template + Spied& operator+=(U value) { + return *this = *this + value; + } + + template + Spied& operator-=(U value) { + return *this = *this - value; + } + + template + Spied& operator*=(U value) { + return *this = *this * value; + } + + template + Spied& operator/=(U value) { + return *this = *this / value; + } + + template + Spied& operator%=(U value) { + return *this = *this % value; + } + +private: + T &value; + std::function write_out; +}; + +template<> +class Spied { +public: + Spied(coord::phys3_delta &value, curve::CurveRecord &watcher, id_t id, const char *name) + : + ne{value.ne, [&value, &watcher, id, name](coord::phys_t v) { watcher.write_out(id, coord::phys3_delta{v, value.se, value.up}, name); }}, + se{value.se, [&value, &watcher, id, name](coord::phys_t v) { watcher.write_out(id, coord::phys3_delta{value.ne, v, value.up}, name); }}, + up{value.up, [&value, &watcher, id, name](coord::phys_t v) { watcher.write_out(id, coord::phys3_delta{value.ne, value.se, v}, name); }}, + watcher{watcher}, + id{id}, + name{name} { + } + + Spied& operator=(coord::phys3_delta value) { + this->ne = value.ne; + this->se = value.se; + this->up = value.up; + return *this; + } + + operator coord::phys3_delta() const { + return coord::phys3_delta{this->ne, this->se, this->up}; + } + + Spied ne; + Spied se; + Spied up; + +private: + curve::CurveRecord &watcher; + id_t id; + const char *name; +}; + +template +auto operator*(const Spied& op1, T op2) -> decltype(std::declval() * op2) +{ + return op1 * op2; +} + +template +class AttributeSpy { +public: + using type = Attribute&; + + static Attribute& create(curve::CurveRecord&, id_t, Attribute &attr) { + return attr; + } +}; + +template<> +class AttributeSpy { +public: + static constexpr const char *name = "hitpoints"; + using type = AttributeSpy; + using value_type = unsigned int; + + static value_type& val_ref(Attribute &attr) { + static_assert(std::is_same::value, "spied type mismatch"); + return attr.current; + } + + static AttributeSpy create(curve::CurveRecord &watcher, id_t id, Attribute &attr) { + return AttributeSpy{watcher, id, attr}; + } + + AttributeSpy(curve::CurveRecord &watcher, id_t id, Attribute &attr) + : + current{val_ref(attr), watcher, id, AttributeSpy::name}, + max{attr.max}, + hp_bar_height{attr.hp_bar_height} { + } + + Spied current; + unsigned int &max; + float &hp_bar_height; +}; + +template<> +class AttributeSpy { +public: + static constexpr const char *name = "dir"; + using type = AttributeSpy; + using value_type = coord::phys3_delta; + + static value_type& val_ref(Attribute &attr) { + static_assert(std::is_same::value, "spied type mismatch"); + return attr.unit_dir; + } + + static AttributeSpy create(curve::CurveRecord &watcher, id_t id, Attribute &attr) { + return AttributeSpy{watcher, id, attr}; + } + + AttributeSpy(curve::CurveRecord &watcher, id_t id, Attribute &attr) + : + unit_dir{val_ref(attr), watcher, id, AttributeSpy::name} { + } + + Spied unit_dir; +}; + +template<> +class AttributeSpy { +public: + static constexpr const char *name = "pos"; + using value_type = tile_range; +}; + +/** + * return attribute from a container while spying on it + */ +template +typename AttributeSpy::type get_attr(curve::CurveRecord &watcher, id_t id, attr_map_t &map) { + return AttributeSpy::create(watcher, id, get_attr_ref(map)); +} + +/** + * return attribute as constant from a container + */ +template +const Attribute& get_attr(const attr_map_t &map) { + return get_attr_ref(map); +} + +template +typename AttributeSpy::value_type parse_attr(std::istream &stream) { + typename AttributeSpy::value_type val; + stream >> val; + return val; +} + +} // namespace openage diff --git a/libopenage/unit/attribute_watcher.h b/libopenage/unit/attribute_watcher.h new file mode 100644 index 0000000000..7c75a95842 --- /dev/null +++ b/libopenage/unit/attribute_watcher.h @@ -0,0 +1,15 @@ +// Copyright 2016-2016 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +namespace openage { + +inline std::string parse_attr_name(std::istream &stream) { + std::string name; + stream >> name; + return name; +} + +} // namespace openage diff --git a/libopenage/unit/producer.cpp b/libopenage/unit/producer.cpp index 1e7af47551..f4a627a781 100644 --- a/libopenage/unit/producer.cpp +++ b/libopenage/unit/producer.cpp @@ -171,7 +171,7 @@ std::string ObjectProducer::name() const { return this->unit_data.name; } -void ObjectProducer::initialise(Unit *unit, Player &player) { +void ObjectProducer::initialise(curve::CurveRecord &watcher, Unit *unit, Player &player) { ENSURE(this->owner == player, "unit init from a UnitType of a wrong player which breaks tech levels"); // log attributes @@ -190,7 +190,7 @@ void ObjectProducer::initialise(Unit *unit, Player &player) { // hitpoints if available if (this->unit_data.hit_points > 0) { - unit->add_attribute(std::make_shared>(this->unit_data.hit_points)); + unit->add_attribute(std::make_shared>(watcher, unit->id, this->unit_data.hit_points)); } // collectable resources @@ -226,12 +226,12 @@ void ObjectProducer::initialise(Unit *unit, Player &player) { unit->push_action( std::make_unique( unit, - [this, unit, &player]() { + [this, unit, &player, &watcher]() { // modify unit to have dead type UnitType *t = player.get_type(this->dead_unit_id); if (t) { - t->initialise(unit, player); + t->initialise(watcher, unit, player); } } ), @@ -251,7 +251,7 @@ void ObjectProducer::initialise(Unit *unit, Player &player) { } } -TerrainObject *ObjectProducer::place(Unit *u, std::shared_ptr terrain, coord::phys3 init_pos) const { +TerrainObject *ObjectProducer::place(curve::CurveRecord &watcher, Unit *u, std::shared_ptr terrain, coord::phys3 init_pos) const { // create new object with correct base shape if (this->unit_data.selection_shape > 1) { @@ -308,7 +308,7 @@ TerrainObject *ObjectProducer::place(Unit *u, std::shared_ptr terrain, // try to place the obj, it knows best whether it will fit. auto state = this->decay? object_state::placed_no_collision : object_state::placed; - if (u->location->place(terrain, init_pos, state)) { + if (u->location->place(watcher, terrain, init_pos, state)) { if (this->on_create) { this->on_create->play(); } @@ -356,12 +356,12 @@ MovableProducer::MovableProducer(const Player &owner, const GameSpec &spec, cons MovableProducer::~MovableProducer() {} -void MovableProducer::initialise(Unit *unit, Player &player) { +void MovableProducer::initialise(curve::CurveRecord &watcher, Unit *unit, Player &player) { /* * call base function */ - ObjectProducer::initialise(unit, player); + ObjectProducer::initialise(watcher, unit, player); /* * basic attributes @@ -391,8 +391,8 @@ void MovableProducer::initialise(Unit *unit, Player &player) { } } -TerrainObject *MovableProducer::place(Unit *unit, std::shared_ptr terrain, coord::phys3 init_pos) const { - return ObjectProducer::place(unit, terrain, init_pos); +TerrainObject *MovableProducer::place(curve::CurveRecord &watcher, Unit *unit, std::shared_ptr terrain, coord::phys3 init_pos) const { + return ObjectProducer::place(watcher, unit, terrain, init_pos); } LivingProducer::LivingProducer(const Player &owner, const GameSpec &spec, const gamedata::unit_living *ud) @@ -406,19 +406,19 @@ LivingProducer::LivingProducer(const Player &owner, const GameSpec &spec, const LivingProducer::~LivingProducer() {} -void LivingProducer::initialise(Unit *unit, Player &player) { +void LivingProducer::initialise(curve::CurveRecord &watcher, Unit *unit, Player &player) { /* * call base function */ - MovableProducer::initialise(unit, player); + MovableProducer::initialise(watcher,unit, player); // add worker attributes if (this->unit_data.unit_class == gamedata::unit_classes::CIVILIAN) { unit->add_attribute(std::make_shared>()); // add graphic ids for resource actions - auto &gather_attr = unit->get_attribute(); + auto &gather_attr = unit->get_attribute(watcher); gather_attr.current_type = game_resource::wood; gather_attr.capacity = 10.0f; gather_attr.gather_rate = 0.002f; @@ -453,7 +453,7 @@ void LivingProducer::initialise(Unit *unit, Player &player) { unit->add_attribute(std::make_shared>()); // add fishing abilites - auto &gather_attr = unit->get_attribute(); + auto &gather_attr = unit->get_attribute(watcher); gather_attr.current_type = game_resource::food; gather_attr.capacity = 15.0f; gather_attr.gather_rate = 0.002f; @@ -463,8 +463,8 @@ void LivingProducer::initialise(Unit *unit, Player &player) { } } -TerrainObject *LivingProducer::place(Unit *unit, std::shared_ptr terrain, coord::phys3 init_pos) const { - return MovableProducer::place(unit, terrain, init_pos); +TerrainObject *LivingProducer::place(curve::CurveRecord &watcher, Unit *unit, std::shared_ptr terrain, coord::phys3 init_pos) const { + return MovableProducer::place(watcher, unit, terrain, init_pos); } BuildingProducer::BuildingProducer(const Player &owner, const GameSpec &spec, const gamedata::unit_building *ud) @@ -530,7 +530,7 @@ std::string BuildingProducer::name() const { return this->unit_data.name; } -void BuildingProducer::initialise(Unit *unit, Player &player) { +void BuildingProducer::initialise(curve::CurveRecord &watcher, Unit *unit, Player &player) { ENSURE(this->owner == player, "unit init from a UnitType of a wrong player which breaks tech levels"); // log type @@ -554,7 +554,7 @@ void BuildingProducer::initialise(Unit *unit, Player &player) { // garrison and hp for all buildings unit->add_attribute(std::make_shared>()); - unit->add_attribute(std::make_shared>(this->unit_data.hit_points)); + unit->add_attribute(std::make_shared>(watcher, unit->id, this->unit_data.hit_points)); bool has_destruct_graphic = this->destroyed != nullptr; unit->push_action(std::make_unique(unit, has_destruct_graphic), true); @@ -602,7 +602,7 @@ std::vector BuildingProducer::get_accepted_resources() { return std::vector(); } -TerrainObject *BuildingProducer::place(Unit *u, std::shared_ptr terrain, coord::phys3 init_pos) const { +TerrainObject *BuildingProducer::place(curve::CurveRecord &watcher, Unit *u, std::shared_ptr terrain, coord::phys3 init_pos) const { // buildings have a square base u->make_location(this->foundation_size, this->terrain_outline); @@ -641,7 +641,7 @@ TerrainObject *BuildingProducer::place(Unit *u, std::shared_ptr terrain // try to place the obj, it knows best whether it will fit. auto state = object_state::floating; - if (!u->location->place(terrain, init_pos, state)) { + if (!u->location->place(watcher, terrain, init_pos, state)) { return nullptr; } @@ -654,7 +654,7 @@ TerrainObject *BuildingProducer::place(Unit *u, std::shared_ptr terrain coord::phys3 a_pos = u->location->pos.draw; a_pos.ne += annex.misplaced0 * coord::settings::phys_per_tile; a_pos.se += annex.misplaced1 * coord::settings::phys_per_tile; - this->make_annex(*u, terrain, annex.unit_id, a_pos, i == 0); + this->make_annex(watcher, *u, terrain, annex.unit_id, a_pos, i == 0); } } @@ -665,7 +665,7 @@ TerrainObject *BuildingProducer::place(Unit *u, std::shared_ptr terrain return u->location.get(); } -TerrainObject *BuildingProducer::make_annex(Unit &u, std::shared_ptr t, int annex_id, coord::phys3 annex_pos, bool c) const { +TerrainObject *BuildingProducer::make_annex(curve::CurveRecord &watcher, Unit &u, std::shared_ptr t, int annex_id, coord::phys3 annex_pos, bool c) const { // for use in lambda drawing functions auto annex_type = this->owner.get_type(annex_id); @@ -685,7 +685,7 @@ TerrainObject *BuildingProducer::make_annex(Unit &u, std::shared_ptr t, // create and place on terrain TerrainObject *annex_loc = u.location->make_annex(annex_foundation); object_state state = c? object_state::placed : object_state::placed_no_collision; - annex_loc->place(t, start_tile, state); + annex_loc->place(watcher, t, start_tile, state); // create special drawing functions for annexes, annex_loc->draw = [annex_loc, annex_type, &u, c]() { @@ -741,7 +741,7 @@ std::string ProjectileProducer::name() const { return this->unit_data.name; } -void ProjectileProducer::initialise(Unit *unit, Player &player) { +void ProjectileProducer::initialise(curve::CurveRecord &, Unit *unit, Player &player) { ENSURE(this->owner == player, "unit init from a UnitType of a wrong player which breaks tech levels"); // initialize graphic set @@ -759,7 +759,7 @@ void ProjectileProducer::initialise(Unit *unit, Player &player) { } } -TerrainObject *ProjectileProducer::place(Unit *u, std::shared_ptr terrain, coord::phys3 init_pos) const { +TerrainObject *ProjectileProducer::place(curve::CurveRecord &watcher, Unit *u, std::shared_ptr terrain, coord::phys3 init_pos) const { /* * radial base shape without collision checking */ @@ -811,7 +811,7 @@ TerrainObject *ProjectileProducer::place(Unit *u, std::shared_ptr terra }; // try to place the obj, it knows best whether it will fit. - if (u->location->place(terrain, init_pos, object_state::placed_no_collision)) { + if (u->location->place(watcher, terrain, init_pos, object_state::placed_no_collision)) { return u->location.get(); } return nullptr; diff --git a/libopenage/unit/producer.h b/libopenage/unit/producer.h index 41ac40ab66..83a11db680 100644 --- a/libopenage/unit/producer.h +++ b/libopenage/unit/producer.h @@ -40,8 +40,8 @@ class ObjectProducer: public UnitType { int id() const override; int parent_id() const override; std::string name() const override; - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; + void initialise(curve::CurveRecord &, Unit *, Player &) override; + TerrainObject *place(curve::CurveRecord &watcher, Unit *, std::shared_ptr, coord::phys3) const override; protected: const GameSpec &dataspec; @@ -71,8 +71,8 @@ class MovableProducer: public ObjectProducer { MovableProducer(const Player &owner, const GameSpec &spec, const gamedata::unit_movable *); virtual ~MovableProducer(); - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; + void initialise(curve::CurveRecord &, Unit *, Player &) override; + TerrainObject *place(curve::CurveRecord &watcher, Unit *, std::shared_ptr, coord::phys3) const override; protected: const gamedata::unit_movable unit_data; @@ -94,8 +94,8 @@ class LivingProducer: public MovableProducer { LivingProducer(const Player &owner, const GameSpec &spec, const gamedata::unit_living *); virtual ~LivingProducer(); - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; + void initialise(curve::CurveRecord &, Unit *, Player &) override; + TerrainObject *place(curve::CurveRecord &watcher, Unit *, std::shared_ptr, coord::phys3) const override; private: const gamedata::unit_living unit_data; @@ -114,8 +114,8 @@ class BuildingProducer: public UnitType { int id() const override; int parent_id() const override; std::string name() const override; - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; + void initialise(curve::CurveRecord &, Unit *, Player &) override; + TerrainObject *place(curve::CurveRecord &watcher, Unit *, std::shared_ptr, coord::phys3) const override; private: const GameSpec &dataspec; @@ -141,7 +141,7 @@ class BuildingProducer: public UnitType { */ bool enable_collisions; - TerrainObject *make_annex(Unit &u, std::shared_ptr t, int annex_id, coord::phys3 annex_pos, bool c) const; + TerrainObject *make_annex(curve::CurveRecord &watcher, Unit &u, std::shared_ptr t, int annex_id, coord::phys3 annex_pos, bool c) const; }; /** @@ -156,8 +156,8 @@ class ProjectileProducer: public UnitType { int id() const override; int parent_id() const override; std::string name() const override; - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; + void initialise(curve::CurveRecord &, Unit *, Player &) override; + TerrainObject *place(curve::CurveRecord &watcher, Unit *, std::shared_ptr, coord::phys3) const override; private: const gamedata::unit_projectile unit_data; diff --git a/libopenage/unit/selection.cpp b/libopenage/unit/selection.cpp index a97cc994ed..537219e6a7 100644 --- a/libopenage/unit/selection.cpp +++ b/libopenage/unit/selection.cpp @@ -49,7 +49,7 @@ bool UnitSelection::on_drawhud() { float percent = static_cast(hp.current) / static_cast(hp.max); int mid = percent * 28.0f - 14.0f; - coord::phys3 &pos_phys3 = unit_ptr->location->pos.draw; + const coord::phys3 &pos_phys3 = unit_ptr->location->pos.draw; coord::camhud pos = pos_phys3.to_camgame().to_window().to_camhud(); glColor3f(0.0, 1.0, 0.0); glBegin(GL_LINES); { diff --git a/libopenage/unit/unit.cpp b/libopenage/unit/unit.cpp index 6c722bb148..f738a3b7f6 100644 --- a/libopenage/unit/unit.cpp +++ b/libopenage/unit/unit.cpp @@ -73,7 +73,7 @@ UnitAction *Unit::before(const UnitAction *action) const { return nullptr; } -bool Unit::update() { +bool Unit::update(curve::CurveRecord &watcher) { // if unit is not on the map then do nothing if (!this->location) { @@ -96,12 +96,12 @@ bool Unit::update() { // TODO: change the entire unit action timing to a higher resolution like // nsecs or usecs. auto time_elapsed = engine.lastframe_duration_nsec() / 1e6; - this->top()->update(time_elapsed); + this->top()->update(watcher, time_elapsed); // the top primary action specifies whether // secondary actions are updated if (this->top()->allow_control()) { - this->update_secondary(time_elapsed); + this->update_secondary(watcher, time_elapsed); } // check completion of all primary actions, @@ -113,42 +113,42 @@ bool Unit::update() { } // apply new queued commands - this->apply_all_cmds(); + this->apply_all_cmds(watcher); return true; } -void Unit::update_secondary(int64_t time_elapsed) { +void Unit::update_secondary(curve::CurveRecord &watcher, int64_t time_elapsed) { // update secondary actions and remove when completed auto position_it = std::remove_if( std::begin(this->action_secondary), std::end(this->action_secondary), - [time_elapsed](std::unique_ptr &action) { - action->update(time_elapsed); + [&watcher, time_elapsed](std::unique_ptr &action) { + action->update(watcher, time_elapsed); return action->completed(); }); this->action_secondary.erase(position_it, std::end(this->action_secondary)); } -void Unit::apply_all_cmds() { +void Unit::apply_all_cmds(curve::CurveRecord &watcher) { std::lock_guard lock(this->command_queue_lock); while (!this->command_queue.empty()) { auto &action = this->command_queue.front(); - this->apply_cmd(action.first, action.second); + this->apply_cmd(watcher, action.first, action.second); this->command_queue.pop(); } } -void Unit::apply_cmd(std::shared_ptr ability, const Command &cmd) { +void Unit::apply_cmd(curve::CurveRecord &watcher, std::shared_ptr ability, const Command &cmd) { bool is_direct = cmd.has_flag(command_flag::direct); if (is_direct) { // drop other actions if a new action is found this->stop_actions(); } - ability->invoke(*this, cmd, is_direct); + ability->invoke(watcher, *this, cmd, is_direct); } diff --git a/libopenage/unit/unit.h b/libopenage/unit/unit.h index 2857c0cfb0..6c8d2d9ab4 100644 --- a/libopenage/unit/unit.h +++ b/libopenage/unit/unit.h @@ -14,6 +14,7 @@ #include "../handlers.h" #include "ability.h" #include "attribute.h" +#include "attribute_spy.h" #include "command.h" #include "unit_container.h" @@ -120,7 +121,7 @@ class Unit : public log::LogSource { /** * update this object using the action currently on top of the stack */ - bool update(); + bool update(curve::CurveRecord &watcher); /** * draws this action by taking the graphic type of the top action @@ -177,11 +178,27 @@ class Unit : public log::LogSource { bool has_attribute(attr_type type) const; /** - * returns attribute based on templated value + * returns attribute based on templated value while spying on it */ template - Attribute &get_attribute() { - return *reinterpret_cast *>(attribute_map[T].get()); + typename AttributeSpy::type get_attribute(curve::CurveRecord &watcher) { + return get_attr(watcher, this->id, this->attribute_map); + } + + /** + * returns attribute as constant based on templated value + */ + template + const Attribute& get_attribute() const { + return get_attr(this->attribute_map); + } + + /** + * returns mutable attribute based on templated value + */ + template + Attribute& get_attribute_unwatched() { + return get_attr_ref(this->attribute_map); } /** @@ -286,18 +303,18 @@ class Unit : public log::LogSource { /** * applies new commands as part of the units update process */ - void apply_all_cmds(); + void apply_all_cmds(curve::CurveRecord &watcher); /** * applies one command using a chosen ability * locks the command queue mutex while operating */ - void apply_cmd(std::shared_ptr ability, const Command &cmd); + void apply_cmd(curve::CurveRecord &watcher, std::shared_ptr ability, const Command &cmd); /** * update all secondary actions */ - void update_secondary(int64_t time_elapsed); + void update_secondary(curve::CurveRecord &watcher, int64_t time_elapsed); /** * erase from action specified by func to the end of the stack diff --git a/libopenage/unit/unit_container.cpp b/libopenage/unit/unit_container.cpp index a16948b1c7..76af70773f 100644 --- a/libopenage/unit/unit_container.cpp +++ b/libopenage/unit/unit_container.cpp @@ -91,16 +91,17 @@ UnitReference UnitContainer::new_unit() { return this->live_units[id]->get_ref(); } -UnitReference UnitContainer::new_unit(UnitType &type, +UnitReference UnitContainer::new_unit(curve::CurveRecord &watcher, + UnitType &type, Player &owner, coord::phys3 position) { auto newobj = std::make_unique(*this, next_new_id++); // try placing unit at this location auto terrain_shared = this->get_terrain(); - auto placed = type.place(newobj.get(), terrain_shared, position); + auto placed = type.place(watcher, newobj.get(), terrain_shared, position); if (placed) { - type.initialise(newobj.get(), owner); + type.initialise(watcher, newobj.get(), owner); auto id = newobj->id; this->live_units.emplace(id, std::move(newobj)); return this->live_units[id]->get_ref(); @@ -108,15 +109,16 @@ UnitReference UnitContainer::new_unit(UnitType &type, return UnitReference(); // is not valid } -UnitReference UnitContainer::new_unit(UnitType &type, +UnitReference UnitContainer::new_unit(curve::CurveRecord &watcher, + UnitType &type, Player &owner, TerrainObject *other) { auto newobj = std::make_unique(*this, next_new_id++); // try placing unit - TerrainObject *placed = type.place_beside(newobj.get(), other); + TerrainObject *placed = type.place_beside(watcher, newobj.get(), other); if (placed) { - type.initialise(newobj.get(), owner); + type.initialise(watcher, newobj.get(), owner); auto id = newobj->id; this->live_units.emplace(id, std::move(newobj)); return this->live_units[id]->get_ref(); @@ -129,11 +131,11 @@ bool dispatch_command(id_t, const Command &) { return true; } -bool UnitContainer::update_all() { +bool UnitContainer::update_all(curve::CurveRecord &watcher) { // update everything and find objects with no actions std::vector to_remove; for (auto &obj : this->live_units) { - obj.second->update(); + obj.second->update(watcher); if ( !obj.second->has_action() ) { to_remove.push_back(obj.first); } diff --git a/libopenage/unit/unit_container.h b/libopenage/unit/unit_container.h index 91e5072b78..3f99875ae0 100644 --- a/libopenage/unit/unit_container.h +++ b/libopenage/unit/unit_container.h @@ -18,6 +18,10 @@ class Unit; class UnitContainer; class UnitType; +namespace curve { +class CurveRecord; +} // namespace curve + using id_t = unsigned long int; /** @@ -100,13 +104,13 @@ class UnitContainer { /** * adds a new unit to the container and initialises using a unit type */ - UnitReference new_unit(UnitType &type, Player &owner, coord::phys3 position); + UnitReference new_unit(curve::CurveRecord &watcher, UnitType &type, Player &owner, coord::phys3 position); /** * adds a new unit to the container and initialises using a unit type * places outside an existing object using the player of that object */ - UnitReference new_unit(UnitType &type, Player &owner, TerrainObject *other); + UnitReference new_unit(curve::CurveRecord &watcher, UnitType &type, Player &owner, TerrainObject *other); /** * give a command to a unit -- unit creation and deletion should be done as commands @@ -116,8 +120,9 @@ class UnitContainer { /** * update dispatched by the game engine * this will update all game objects + * while spying on their attributes */ - bool update_all(); + bool update_all(curve::CurveRecord &watcher); /** * gets a list of all units in the container diff --git a/libopenage/unit/unit_type.cpp b/libopenage/unit/unit_type.cpp index 9daf8d0af0..39c544c853 100644 --- a/libopenage/unit/unit_type.cpp +++ b/libopenage/unit/unit_type.cpp @@ -41,7 +41,7 @@ UnitTexture *UnitType::default_texture() { return this->graphics[graphic_type::standing].get(); } -TerrainObject *UnitType::place_beside(Unit *u, TerrainObject const *other) const { +TerrainObject *UnitType::place_beside(curve::CurveRecord &watcher, Unit *u, TerrainObject const *other) const { if (!u || !other) { return nullptr; } @@ -60,7 +60,7 @@ TerrainObject *UnitType::place_beside(Unit *u, TerrainObject const *other) const continue; } - auto placed = this->place(u, terrain, temp_pos.to_phys2().to_phys3()); + auto placed = this->place(watcher, u, terrain, temp_pos.to_phys2().to_phys3()); if (placed) { return placed; } @@ -102,7 +102,7 @@ std::string NyanType::name() const { return "Nyan"; } -void NyanType::initialise(Unit *unit, Player &) { +void NyanType::initialise(curve::CurveRecord &, Unit *unit, Player &) { // reset any existing attributes and type unit->reset(); @@ -122,7 +122,7 @@ void NyanType::initialise(Unit *unit, Player &) { unit->push_action(std::make_unique(unit), true); } -TerrainObject *NyanType::place(Unit *unit, std::shared_ptr terrain, coord::phys3 init_pos) const { +TerrainObject *NyanType::place(curve::CurveRecord &watcher, Unit *unit, std::shared_ptr terrain, coord::phys3 init_pos) const { // the parsed nyan data gives the rules for terrain placement // which includes valid terrains, base radius and shape @@ -134,7 +134,7 @@ TerrainObject *NyanType::place(Unit *unit, std::shared_ptr terrain, coo }; // try to place the obj, it knows best whether it will fit. - if (unit->location->place(terrain, init_pos, object_state::placed)) { + if (unit->location->place(watcher, terrain, init_pos, object_state::placed)) { return unit->location.get(); } diff --git a/libopenage/unit/unit_type.h b/libopenage/unit/unit_type.h index f15a886484..8e2fda2cbd 100644 --- a/libopenage/unit/unit_type.h +++ b/libopenage/unit/unit_type.h @@ -84,7 +84,7 @@ class UnitType { * * TODO: make const */ - virtual void initialise(Unit *, Player &) = 0; + virtual void initialise(curve::CurveRecord &, Unit *, Player &) = 0; /** * set unit in place -- return if placement was successful @@ -93,7 +93,7 @@ class UnitType { * when a unit is ungarrsioned from a building or object * TODO: make const */ - virtual TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const = 0; + virtual TerrainObject *place(curve::CurveRecord &watcher, Unit *, std::shared_ptr, coord::phys3) const = 0; /** * compare if two types are the same @@ -109,7 +109,7 @@ class UnitType { /** * similiar to place but places adjacent to an existing object */ - TerrainObject *place_beside(Unit *, TerrainObject const *) const; + TerrainObject *place_beside(curve::CurveRecord &watcher, Unit *, TerrainObject const *) const; /** * copy attributes of this unit type to a new unit instance @@ -178,8 +178,8 @@ class NyanType: public UnitType { int id() const override; int parent_id() const override; std::string name() const override; - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; + void initialise(curve::CurveRecord &, Unit *, Player &) override; + TerrainObject *place(curve::CurveRecord &watcher, Unit *, std::shared_ptr, coord::phys3) const override; }; diff --git a/openage/convert/driver.py b/openage/convert/driver.py index 08cbe1e660..ad742c3a47 100644 --- a/openage/convert/driver.py +++ b/openage/convert/driver.py @@ -95,7 +95,7 @@ def get_string_resources(args): elif srcdir["language.dll"].is_file(): from .pefile import PEFile - for name in ["language.dll", "language_x1.dll", "language_x1_p1.dll"]: + for name in ["language.dll", "language_x1.dll"]: pefile = PEFile(srcdir[name].open('rb')) stringres.fill_from(pefile.resources().strings) count += 1 @@ -126,9 +126,9 @@ def get_blendomatic_data(srcdir): def get_gamespec(srcdir, dont_pickle): """ reads empires.dat and fixes it """ - cache_file = os.path.join(gettempdir(), "empires2_x1_p1.dat.pickle") + cache_file = os.path.join(gettempdir(), "empires2_x1.dat.pickle") - with srcdir["data/empires2_x1_p1.dat"].open('rb') as empiresdat_file: + with srcdir["data/empires2_x1.dat"].open('rb') as empiresdat_file: gamespec = load_gamespec(empiresdat_file, cache_file, not dont_pickle) # modify the read contents of datfile diff --git a/openage/convert/main.py b/openage/convert/main.py index fa18c78f67..b7c7ad5bdd 100644 --- a/openage/convert/main.py +++ b/openage/convert/main.py @@ -79,7 +79,7 @@ def mount_drs(filename, target, ignore_nonexistant=False): if GameVersion.age2_hd_3x not in game_versions: mount_drs("data/gamedata.drs", "gamedata") mount_drs("data/gamedata_x1.drs", "gamedata") - mount_drs("data/gamedata_x1_p1.drs", "gamedata") +# mount_drs("data/gamedata_x1_p1.drs", "gamedata") # TODO mount gamedata_x2.drs and _x2_p1 if they exist? return result diff --git a/openage/convert/slp_converter_pool.py b/openage/convert/slp_converter_pool.py index 705ab0887d..514d51ccda 100644 --- a/openage/convert/slp_converter_pool.py +++ b/openage/convert/slp_converter_pool.py @@ -99,15 +99,15 @@ def convert(self, slpdata, custom_cutter=None): inqueue, outqueue = self.idle.get() - with self.job_mutex: - inqueue.put((slpdata, custom_cutter)) + #with self.job_mutex: + inqueue.put((slpdata, custom_cutter)) # TODO not sure why this synchronization is needed. # (But it is. otherwise, there are non-deterministic crashes when # depickling some of Texture's numpy internals, especially with # high job counts.). - with self.job_mutex: - result = outqueue.get() + #with self.job_mutex: + result = outqueue.get() self.idle.put((inqueue, outqueue)) diff --git a/openage/testing/testlist.py b/openage/testing/testlist.py index d6bb2a3dd8..056fd0ccd8 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -67,6 +67,7 @@ def tests_cpp(): yield "openage::util::tests::matrix" yield "openage::util::tests::vector" yield "openage::input::tests::parse_event_string", "keybinds parsing" + yield "openage::curve::tests::advance_frame", "moving interpolator's frame" def demos_cpp():